综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

201 строка
8.2 KiB

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