You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

427 lines
11 KiB

  1. /*******************************
  2. * Copyright (C) 2025.
  3. *
  4. * File Name: serialcommunicator.cpp
  5. * Description: 串口通信管理类实现文件
  6. * Others:
  7. * Version: 1.0
  8. * Author: lipengpeng
  9. * Date: 2025-7-23
  10. *******************************/
  11. #include "serialcommunicator.h"
  12. /**
  13. * @brief 构造函数
  14. * @param parent 父对象指针
  15. *
  16. * @details 初始化串口通信组件,包括:
  17. * 1. 创建串口对象
  18. * 2. 创建接收和重发定时器
  19. * 3. 设置默认超时参数
  20. * 4. 连接信号槽
  21. */
  22. SerialCommunicator::SerialCommunicator(QObject *parent) : QObject(parent)
  23. , serialPort_(new QSerialPort(this))
  24. , recvTimer_(new QTimer(this))
  25. , resendTimer_(new QTimer(this))
  26. , stationCheck_(new QTimer(this))
  27. , checkTimeOut_(new QTimer(this))
  28. , comCount_(0)
  29. , maxRetry_(3)
  30. , recvTimeout_(50)
  31. , resendTimeout_(1000)
  32. , test_(QByteArray::fromHex("010100010001AC0A"))
  33. {
  34. // 配置定时器(单次触发)
  35. recvTimer_->setSingleShot(true);
  36. resendTimer_->setSingleShot(true);
  37. stationCheck_->setSingleShot(true);
  38. // 连接信号槽
  39. connect(serialPort_, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead);
  40. connect(recvTimer_, &QTimer::timeout, this, &SerialCommunicator::onRecvTimeout);
  41. connect(resendTimer_, &QTimer::timeout, this, &SerialCommunicator::onResendTimeout);
  42. connect(stationCheck_, QTimer::timeout, this, SerialCommunicator::stationCheck);
  43. connect(checkTimeOut_, QTimer::timeout, this, SerialCommunicator::checkTimeOut);
  44. connect(serialPort_, SIGNAL(error(QSerialPort::SerialPortError)),
  45. this, SLOT(handleSerialError(QSerialPort::SerialPortError)));
  46. }
  47. /**
  48. * @brief 析构函数
  49. * @note 确保串口关闭并释放资源
  50. */
  51. SerialCommunicator::~SerialCommunicator()
  52. {
  53. close();
  54. }
  55. /**
  56. * @brief 设置串口参数
  57. * @param portName 串口号(如"COM1")
  58. * @param baudRate 波特率(如9600)
  59. * @param dataBits 数据位
  60. * @param parity 校验位
  61. * @param stopBits 停止位
  62. *
  63. * @note 此方法应在打开串口前调用,设置值将在下次open()时生效
  64. */
  65. void SerialCommunicator::setPortParams(QString portName, qint32 baudRate,
  66. QSerialPort::DataBits dataBits,
  67. QSerialPort::Parity parity,
  68. QSerialPort::StopBits stopBits)
  69. {
  70. this->portName_ = portName;
  71. this->baudRate_ = baudRate;
  72. this->dataBits_ = dataBits;
  73. this->parity_ = parity;
  74. this->stopBits_ = stopBits;
  75. }
  76. /**
  77. * @brief 打开串口
  78. * @return true-打开成功, false-打开失败
  79. *
  80. * @details 执行流程:
  81. * 1. 关闭已打开的串口(如果存在)
  82. * 2. 配置串口参数(波特率、数据位等)
  83. * 3. 尝试打开串口
  84. * 4. 发送状态变化信号
  85. */
  86. bool SerialCommunicator::open()
  87. {
  88. // 关闭已打开的串口(如果存在)
  89. if (serialPort_->isOpen())
  90. {
  91. serialPort_->close();
  92. }
  93. // 配置串口参数
  94. serialPort_->setPortName(portName_);
  95. if (!serialPort_->setBaudRate(baudRate_))
  96. {
  97. emit statusChanged("波特率设置失败");
  98. return false;
  99. }
  100. if (!serialPort_->setDataBits(dataBits_))
  101. {
  102. emit statusChanged("数据位设置失败");
  103. return false;
  104. }
  105. if (!serialPort_->setParity(parity_))
  106. {
  107. emit statusChanged("校验位设置失败");
  108. return false;
  109. }
  110. if (!serialPort_->setStopBits(stopBits_))
  111. {
  112. emit statusChanged("停止位设置失败");
  113. return false;
  114. }
  115. serialPort_->setFlowControl(QSerialPort::NoFlowControl); // 无流控制
  116. // 打开串口
  117. if (serialPort_->open(QIODevice::ReadWrite))
  118. {
  119. emit statusChanged("串口连接成功");
  120. stationCheck_->start(1000);
  121. return true;
  122. }
  123. else
  124. {
  125. emit statusChanged("串口连接失败,请检查参数");
  126. return false;
  127. }
  128. }
  129. /**
  130. * @brief 关闭串口
  131. *
  132. * @details 执行流程:
  133. * 1. 关闭所有定时器
  134. * 2. 清理待处理数据
  135. * 3. 检查串口是否打开
  136. * 4. 关闭串口
  137. * 5. 发送状态变化信号
  138. */
  139. void SerialCommunicator::close()
  140. {
  141. if (serialPort_->isOpen())
  142. {
  143. //关闭所有定时器
  144. recvTimer_->stop();
  145. resendTimer_->stop();
  146. stationCheck_->stop();
  147. checkTimeOut_->stop();
  148. emit stationConnect(false);
  149. // 清除可能存在的待处理数据
  150. serialPort_->clear();
  151. // 关闭串口
  152. serialPort_->close();
  153. emit statusChanged("串口断开");
  154. comCount_ = 0; // 重置通信计数器
  155. }
  156. }
  157. /**
  158. * @brief 发送数据并启动超时重发机制
  159. * @param data 待发送的数据
  160. *
  161. * @details 执行流程:
  162. * 1. 检查串口是否打开
  163. * 2. 保存待发送数据(用于重发)
  164. * 3. 重置重发计数器
  165. * 4. 发送数据
  166. * 5. 启动重发超时定时器
  167. */
  168. void SerialCommunicator::sendData(const QByteArray &data)
  169. {
  170. if (!serialPort_->isOpen())
  171. {
  172. emit statusChanged("串口未打开,无法发送数据");
  173. return;
  174. }
  175. stationCheck_->stop();
  176. checkTimeOut_->stop();
  177. // 保存待重发数据,初始化计数器,启动重发定时器
  178. currentData_ = data;
  179. comCount_ = 0;
  180. serialPort_->write(data); // 发送数据
  181. emit statusChanged("发送报文:" + data.toHex().toUpper());
  182. resendTimer_->start(resendTimeout_); // 启动超时计时
  183. }
  184. /**
  185. * @brief 设置最大重发次数
  186. * @param count 最大重发次数
  187. */
  188. void SerialCommunicator::setMaxRetry(int count)
  189. {
  190. if (count < 0)
  191. {
  192. return;
  193. }
  194. maxRetry_ = count;
  195. }
  196. /**
  197. * @brief 设置接收完成超时时间
  198. * @param ms 超时时间(毫秒)
  199. *
  200. * @note 当串口持续ms毫秒无新数据时,认为接收完成
  201. */
  202. void SerialCommunicator::setRecvTimeout(int ms)
  203. {
  204. if (ms < 0)
  205. {
  206. return;
  207. }
  208. recvTimeout_ = ms;
  209. }
  210. /**
  211. * @brief 设置重发间隔时间
  212. * @param ms 重发间隔(毫秒)
  213. */
  214. void SerialCommunicator::setResendTimeout(int ms)
  215. {
  216. if (ms < 0)
  217. {
  218. return;
  219. }
  220. resendTimeout_ = ms;
  221. }
  222. /**
  223. * @brief 检查串口是否打开
  224. * @return true-已打开, false-已关闭
  225. */
  226. bool SerialCommunicator::isOpen() const
  227. {
  228. return serialPort_->isOpen();
  229. }
  230. void SerialCommunicator::setCheckMsg(QByteArray msg)
  231. {
  232. this->test_ = msg;
  233. }
  234. // ====================== 私有槽函数 ====================== //
  235. /**
  236. * @brief 串口数据到达处理
  237. *
  238. * @details 执行流程:
  239. * 1. 读取所有可用数据到接收缓冲区
  240. * 2. 重启接收超时定时器
  241. * 3. 定时器超时后将触发onRecvTimeout
  242. */
  243. void SerialCommunicator::onReadyRead()
  244. {
  245. if(checkTimeOut_->isActive() || stationCheck_->isActive())
  246. {
  247. checkTimeOut_->stop();
  248. serialPort_->clear();
  249. emit stationConnect(true);
  250. stationCheck_->start(1000);
  251. }
  252. else
  253. {
  254. // 读取数据到缓冲区
  255. recvBuffer_.append(serialPort_->readAll());
  256. //重置接收超时定时器
  257. recvTimer_->start(recvTimeout_);
  258. }
  259. }
  260. /**
  261. * @brief 数据接收完成处理
  262. *
  263. * @details 执行流程:
  264. * 1. 停止重发定时器(表示已收到响应)
  265. * 2. 发送dataReceived信号包含完整数据
  266. * 3. 清空接收缓冲区
  267. */
  268. void SerialCommunicator::onRecvTimeout()
  269. {
  270. // 接收完成,发送完整数据信号,停止重发定时器
  271. stationCheck_->start(1000);
  272. resendTimer_->stop();
  273. emit dataReceived(recvBuffer_);
  274. recvBuffer_.clear();
  275. }
  276. /**
  277. * @brief 重发超时处理
  278. *
  279. * @details 执行流程:
  280. * 1. 检查是否达到最大重发次数
  281. * 2. 未达到则重发数据并更新计数器
  282. * 3. 达到最大次数则触发超时信号
  283. */
  284. void SerialCommunicator::onResendTimeout()
  285. {
  286. if (comCount_ < maxRetry_)
  287. {
  288. // 重发数据,更新计数器
  289. serialPort_->write(currentData_);
  290. comCount_++;
  291. emit statusChanged(QString("通信超时,重新发送中(%1/%2)").arg(comCount_).arg(maxRetry_));
  292. resendTimer_->start(resendTimeout_); // 继续计时
  293. }
  294. else
  295. {
  296. // 达到最大重发次数,触发超时信号
  297. resendTimer_->stop();
  298. stationCheck_->start(1000);
  299. emit timeoutOccurred();
  300. }
  301. }
  302. /**
  303. * @brief 处理串口错误
  304. * @param error 串口错误代码
  305. *
  306. * @details 执行流程:
  307. * 1. 忽略无错误和超时错误(超时由其他机制处理)
  308. * 2. 根据错误代码生成错误描述
  309. * 3. 对于物理断开错误(ResourceError):
  310. * - 自动关闭串口
  311. * - 停止所有定时器
  312. * - 清空接收缓冲区
  313. * - 发送物理断开信号
  314. * 4. 其他错误只发送状态通知
  315. */
  316. void SerialCommunicator::handleSerialError(QSerialPort::SerialPortError error)
  317. {
  318. // 忽略无错误和超时错误(超时由其他机制处理)
  319. if (error == QSerialPort::NoError || error == QSerialPort::TimeoutError)
  320. {
  321. return;
  322. }
  323. QString errorMsg;
  324. switch (error)
  325. {
  326. case QSerialPort::DeviceNotFoundError:
  327. errorMsg = "设备未找到";
  328. break;
  329. case QSerialPort::PermissionError:
  330. errorMsg = "没有权限访问设备";
  331. break;
  332. case QSerialPort::OpenError:
  333. errorMsg = "设备已打开";
  334. break;
  335. case QSerialPort::ParityError:
  336. errorMsg = "奇偶校验错误";
  337. break;
  338. case QSerialPort::FramingError:
  339. errorMsg = "帧错误";
  340. break;
  341. case QSerialPort::BreakConditionError:
  342. errorMsg = "Break条件错误";
  343. break;
  344. case QSerialPort::WriteError:
  345. errorMsg = "写入错误";
  346. break;
  347. case QSerialPort::ReadError:
  348. errorMsg = "读取错误";
  349. break;
  350. case QSerialPort::ResourceError:
  351. errorMsg = "资源错误(设备已断开)";
  352. break;
  353. case QSerialPort::UnsupportedOperationError:
  354. errorMsg = "不支持的操作";
  355. break;
  356. case QSerialPort::UnknownError:
  357. errorMsg = "未知错误";
  358. break;
  359. case QSerialPort::NotOpenError:
  360. errorMsg = "设备未打开";
  361. break;
  362. default:
  363. errorMsg = "未定义错误";
  364. }
  365. // 物理断开处理(资源错误)
  366. if (error == QSerialPort::ResourceError)
  367. {
  368. // 自动关闭串口
  369. close();
  370. // 停止所有定时器
  371. recvTimer_->stop();
  372. resendTimer_->stop();
  373. // 清空缓冲区
  374. recvBuffer_.clear();
  375. // 通知上层
  376. emit statusChanged("物理连接断开: " + errorMsg);
  377. emit physicalDisconnected();
  378. }
  379. else
  380. {
  381. // 其他错误只通知不自动断开
  382. emit statusChanged("串口错误: " + errorMsg);
  383. }
  384. }
  385. void SerialCommunicator::stationCheck()
  386. {
  387. serialPort_->write(test_);
  388. checkTimeOut_->setSingleShot(true);
  389. checkTimeOut_->start(200);
  390. }
  391. void SerialCommunicator::checkTimeOut()
  392. {
  393. emit stationConnect(false);
  394. stationCheck_->start(1000);
  395. }