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.
 
 
 

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