综合平台编程器项目的远程存储
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.
 
 
 
 

182 rivejä
7.3 KiB

  1. #pragma once
  2. #include "domain/register_address.h"
  3. #include "domain/project_limits.h"
  4. #include <algorithm>
  5. #include <cctype>
  6. #include <functional>
  7. #include <string>
  8. #include <vector>
  9. // PLC 串口和轮询参数;parity 使用 Qt 约定值:0 无、2 偶、3 奇
  10. struct PlcSerialConfiguration
  11. {
  12. std::string portName; // 串口名称,例如 COM3
  13. int serverAddress = 1; // Modbus 从站地址,范围为 1~247
  14. int baudRate = 9600; // 串口波特率,支持 9600、19200、38400、57600 和 115200
  15. int dataBits = 8; // 数据位,支持 7 或 8
  16. int parity = 2; // 校验方式,0 为无校验、2 为偶校验、3 为奇校验
  17. int stopBits = 1; // 停止位,支持 1 或 2
  18. int responseTimeoutMs = 1000; // 单次 Modbus 请求的响应超时时间,单位为毫秒
  19. int retries = 2; // 请求失败后的重试次数,范围为 0~5
  20. int pollIntervalMs = 200; // 轮询周期,单位为毫秒,范围为 50~10000
  21. };
  22. // PLC 异步通信生命周期
  23. enum class PlcConnectionState
  24. {
  25. Disconnected, // 未建立 PLC 连接
  26. Connecting, // 正在异步建立 PLC 连接
  27. Connected, // 串口已连接并进行正常轮询,不代表首读已完成
  28. Recovering, // 从可恢复通信故障中探测恢复并重新进行首读
  29. Faulted // 当前通信发生故障,等待恢复探测或重新连接
  30. };
  31. // 通信故障分类,用于状态栏提示和真机运行资格撤销
  32. enum class PlcCommunicationError
  33. {
  34. None, // 没有待报告的通信错误
  35. SerialPortOpenFailed, // 串口打开失败
  36. PlcNotResponding, // PLC 未响应请求
  37. UsbSerialAdapterRemoved, // USB 转串口设备被移除
  38. SerialConnectionLost, // 已建立的串口连接意外中断
  39. CommunicationTimeout, // 请求等待响应超时
  40. ProtocolError, // 收到的 Modbus 数据不符合协议
  41. ReadFailed, // 读取请求失败
  42. WriteFailed, // 写入请求失败
  43. ConfigurationError, // 串口或通信参数无效
  44. RequestAborted, // 请求在完成前被中止
  45. Unknown // 未分类的通信错误
  46. };
  47. // 通信命令的受理结果;真正的读写完成由回调和缓存更新通知
  48. struct PlcCommunicationResult
  49. {
  50. bool succeeded = false; // 命令是否被接受或参数校验是否通过
  51. std::string message; // 失败原因或补充说明,成功时通常为空
  52. };
  53. /**
  54. * @brief 校验 PLC 串口、Modbus 和轮询参数
  55. *
  56. * 此函数只检查配置,不打开串口、不启动通信,也不会修改传入对象
  57. * @param configuration 待校验的连接配置
  58. * @return 校验通过时返回 succeeded 为 true,否则返回 false 和 UTF-8 错误说明
  59. */
  60. inline PlcCommunicationResult validatePlcSerialConfiguration(
  61. const PlcSerialConfiguration &configuration)
  62. {
  63. const bool has_port_name = std::any_of(
  64. configuration.portName.cbegin(), configuration.portName.cend(),
  65. [](unsigned char character) { return std::isspace(character) == 0; });
  66. if (!has_port_name)
  67. {
  68. return {false, "必须填写串口端口"};
  69. }
  70. if (configuration.serverAddress < ProjectLimits::kMinimumPlcServerAddress
  71. || configuration.serverAddress > ProjectLimits::kMaximumPlcServerAddress)
  72. {
  73. return {false, "PLC 站号必须在 1~247 范围内"};
  74. }
  75. if (configuration.baudRate != 9600
  76. && configuration.baudRate != 19200
  77. && configuration.baudRate != 38400
  78. && configuration.baudRate != 57600
  79. && configuration.baudRate != 115200)
  80. {
  81. return {false, "波特率只支持 9600、19200、38400、57600 或 115200"};
  82. }
  83. if (configuration.dataBits != 7 && configuration.dataBits != 8)
  84. {
  85. return {false, "数据位只支持 7 或 8"};
  86. }
  87. if (configuration.parity != 0
  88. && configuration.parity != 2
  89. && configuration.parity != 3)
  90. {
  91. return {false, "校验方式只支持无校验、偶校验或奇校验"};
  92. }
  93. if (configuration.stopBits != 1 && configuration.stopBits != 2)
  94. {
  95. return {false, "停止位只支持 1 或 2"};
  96. }
  97. if (configuration.responseTimeoutMs < ProjectLimits::kMinimumResponseTimeoutMs
  98. || configuration.responseTimeoutMs > ProjectLimits::kMaximumResponseTimeoutMs)
  99. {
  100. return {false, "PLC 响应超时必须在 100~30000 ms 范围内"};
  101. }
  102. if (configuration.retries < ProjectLimits::kMinimumRetries
  103. || configuration.retries > ProjectLimits::kMaximumRetries)
  104. {
  105. return {false, "PLC 失败重试次数必须在 0~5 范围内"};
  106. }
  107. if (configuration.pollIntervalMs < ProjectLimits::kMinimumPollIntervalMs
  108. || configuration.pollIntervalMs > ProjectLimits::kMaximumPollIntervalMs)
  109. {
  110. return {false, "PLC 轮询周期必须在 50~10000 ms 范围内"};
  111. }
  112. return {true, {}};
  113. }
  114. // UI/运行服务使用的异步 PLC 通信契约,具体实现位于 infrastructure
  115. class PlcCommunicationGateway
  116. {
  117. public:
  118. // 允许通过网关基类指针安全释放具体通信实现
  119. virtual ~PlcCommunicationGateway() = default;
  120. /**
  121. * @brief 校验串口配置并启动异步 PLC 连接
  122. * @param configuration 本次连接使用的串口、Modbus 和轮询参数
  123. * @return true 仅表示连接请求已受理,实际连接结果通过状态回调通知
  124. */
  125. virtual PlcCommunicationResult connectDevice(
  126. const PlcSerialConfiguration &configuration) = 0;
  127. /**
  128. * @brief 停止轮询并断开当前 PLC 连接
  129. *
  130. * 断开后当前缓存视为无效,首读资格同时清除
  131. */
  132. virtual void disconnectDevice() = 0;
  133. /**
  134. * @brief 设置后续轮询的 M/D 地址集合
  135. *
  136. * 实现可以校验、排序、去重并合并相邻地址块;已有读请求进行时,
  137. * 新集合可以延后到当前请求完成后生效
  138. * @param addresses 需要周期性读回的 M/D 地址集合,空集合使用实现的默认探测地址
  139. * @return true 表示集合已接受,false 表示地址或轮询资源校验失败
  140. */
  141. virtual PlcCommunicationResult setPollAddresses(
  142. const std::vector<RegisterAddress> &addresses) = 0;
  143. // 返回当前连接生命周期状态;Connected 不代表首读资格已经完成
  144. virtual PlcConnectionState state() const = 0;
  145. // 返回本次连接是否已完成全部轮询块的成功首读
  146. virtual bool initialReadCompleted() const = 0;
  147. // 返回最近一次通信错误的分类;没有错误或错误已清除时返回 None
  148. virtual PlcCommunicationError lastErrorType() const = 0;
  149. // 返回最近一次通信错误的 UTF-8 可读文本;没有错误时返回空字符串
  150. virtual const std::string &lastError() const = 0;
  151. /**
  152. * @brief 替换异步通信事件回调
  153. *
  154. * 传入空 std::function 可取消对应通知;回调由实现在线程或事件循环中按事件发生时调用
  155. * @param state_changed 连接状态发生变化时调用
  156. * @param initial_read_changed 首读资格发生变化时调用,参数表示当前是否已完成首读
  157. * @param cache_updated 任一轮询块成功更新 PLC 缓存后调用
  158. * @param error_reported 发生通信错误时调用,参数为 UTF-8 可读错误文本
  159. */
  160. virtual void setCallbacks(
  161. std::function<void()> state_changed,
  162. std::function<void(bool)> initial_read_changed,
  163. std::function<void()> cache_updated,
  164. std::function<void(const std::string &)> error_reported) = 0;
  165. };