|
- #pragma once
-
- #include "domain/register_address.h"
- #include "domain/project_limits.h"
-
- #include <algorithm>
- #include <cctype>
- #include <functional>
- #include <string>
- #include <vector>
-
- // PLC 串口和轮询参数;parity 使用 Qt 约定值:0 无、2 偶、3 奇
- struct PlcSerialConfiguration
- {
- std::string portName; // 串口名称,例如 COM3
- int serverAddress = 1; // Modbus 从站地址,范围为 1~247
- int baudRate = 9600; // 串口波特率,支持 9600、19200、38400、57600 和 115200
- int dataBits = 8; // 数据位,支持 7 或 8
- int parity = 2; // 校验方式,0 为无校验、2 为偶校验、3 为奇校验
- int stopBits = 1; // 停止位,支持 1 或 2
- int responseTimeoutMs = 1000; // 单次 Modbus 请求的响应超时时间,单位为毫秒
- int retries = 2; // 请求失败后的重试次数,范围为 0~5
- int pollIntervalMs = 200; // 轮询周期,单位为毫秒,范围为 50~10000
- };
-
- // PLC 异步通信生命周期
- enum class PlcConnectionState
- {
- Disconnected, // 未建立 PLC 连接
- Connecting, // 正在异步建立 PLC 连接
- Connected, // 串口已连接并进行正常轮询,不代表首读已完成
- Recovering, // 从可恢复通信故障中探测恢复并重新进行首读
- Faulted // 当前通信发生故障,等待恢复探测或重新连接
- };
-
- // 通信故障分类,用于状态栏提示和真机运行资格撤销
- enum class PlcCommunicationError
- {
- None, // 没有待报告的通信错误
- SerialPortOpenFailed, // 串口打开失败
- PlcNotResponding, // PLC 未响应请求
- UsbSerialAdapterRemoved, // USB 转串口设备被移除
- SerialConnectionLost, // 已建立的串口连接意外中断
- CommunicationTimeout, // 请求等待响应超时
- ProtocolError, // 收到的 Modbus 数据不符合协议
- ReadFailed, // 读取请求失败
- WriteFailed, // 写入请求失败
- ConfigurationError, // 串口或通信参数无效
- RequestAborted, // 请求在完成前被中止
- Unknown // 未分类的通信错误
- };
-
- // 通信命令的受理结果;真正的读写完成由回调和缓存更新通知
- struct PlcCommunicationResult
- {
- bool succeeded = false; // 命令是否被接受或参数校验是否通过
- std::string message; // 失败原因或补充说明,成功时通常为空
- };
-
- /**
- * @brief 校验 PLC 串口、Modbus 和轮询参数
- *
- * 此函数只检查配置,不打开串口、不启动通信,也不会修改传入对象
- * @param configuration 待校验的连接配置
- * @return 校验通过时返回 succeeded 为 true,否则返回 false 和 UTF-8 错误说明
- */
- inline PlcCommunicationResult validatePlcSerialConfiguration(
- const PlcSerialConfiguration &configuration)
- {
- const bool has_port_name = std::any_of(
- configuration.portName.cbegin(), configuration.portName.cend(),
- [](unsigned char character) { return std::isspace(character) == 0; });
- if (!has_port_name)
- {
- return {false, "必须填写串口端口"};
- }
- if (configuration.serverAddress < ProjectLimits::kMinimumPlcServerAddress
- || configuration.serverAddress > ProjectLimits::kMaximumPlcServerAddress)
- {
- return {false, "PLC 站号必须在 1~247 范围内"};
- }
- if (configuration.baudRate != 9600
- && configuration.baudRate != 19200
- && configuration.baudRate != 38400
- && configuration.baudRate != 57600
- && configuration.baudRate != 115200)
- {
- return {false, "波特率只支持 9600、19200、38400、57600 或 115200"};
- }
- if (configuration.dataBits != 7 && configuration.dataBits != 8)
- {
- return {false, "数据位只支持 7 或 8"};
- }
- if (configuration.parity != 0
- && configuration.parity != 2
- && configuration.parity != 3)
- {
- return {false, "校验方式只支持无校验、偶校验或奇校验"};
- }
- if (configuration.stopBits != 1 && configuration.stopBits != 2)
- {
- return {false, "停止位只支持 1 或 2"};
- }
- if (configuration.responseTimeoutMs < ProjectLimits::kMinimumResponseTimeoutMs
- || configuration.responseTimeoutMs > ProjectLimits::kMaximumResponseTimeoutMs)
- {
- return {false, "PLC 响应超时必须在 100~30000 ms 范围内"};
- }
- if (configuration.retries < ProjectLimits::kMinimumRetries
- || configuration.retries > ProjectLimits::kMaximumRetries)
- {
- return {false, "PLC 失败重试次数必须在 0~5 范围内"};
- }
- if (configuration.pollIntervalMs < ProjectLimits::kMinimumPollIntervalMs
- || configuration.pollIntervalMs > ProjectLimits::kMaximumPollIntervalMs)
- {
- return {false, "PLC 轮询周期必须在 50~10000 ms 范围内"};
- }
- return {true, {}};
- }
-
- // UI/运行服务使用的异步 PLC 通信契约,具体实现位于 infrastructure
- class PlcCommunicationGateway
- {
- public:
- // 允许通过网关基类指针安全释放具体通信实现
- virtual ~PlcCommunicationGateway() = default;
-
- /**
- * @brief 校验串口配置并启动异步 PLC 连接
- * @param configuration 本次连接使用的串口、Modbus 和轮询参数
- * @return true 仅表示连接请求已受理,实际连接结果通过状态回调通知
- */
- virtual PlcCommunicationResult connectDevice(
- const PlcSerialConfiguration &configuration) = 0;
-
- /**
- * @brief 停止轮询并断开当前 PLC 连接
- *
- * 断开后当前缓存视为无效,首读资格同时清除
- */
- virtual void disconnectDevice() = 0;
-
- /**
- * @brief 设置后续轮询的 M/D 地址集合
- *
- * 实现可以校验、排序、去重并合并相邻地址块;已有读请求进行时,
- * 新集合可以延后到当前请求完成后生效
- * @param addresses 需要周期性读回的 M/D 地址集合,空集合使用实现的默认探测地址
- * @return true 表示集合已接受,false 表示地址或轮询资源校验失败
- */
- virtual PlcCommunicationResult setPollAddresses(
- const std::vector<RegisterAddress> &addresses) = 0;
-
- // 返回当前连接生命周期状态;Connected 不代表首读资格已经完成
- virtual PlcConnectionState state() const = 0;
-
- // 返回本次连接是否已完成全部轮询块的成功首读
- virtual bool initialReadCompleted() const = 0;
-
- // 返回最近一次通信错误的分类;没有错误或错误已清除时返回 None
- virtual PlcCommunicationError lastErrorType() const = 0;
-
- // 返回最近一次通信错误的 UTF-8 可读文本;没有错误时返回空字符串
- virtual const std::string &lastError() const = 0;
-
- /**
- * @brief 替换异步通信事件回调
- *
- * 传入空 std::function 可取消对应通知;回调由实现在线程或事件循环中按事件发生时调用
- * @param state_changed 连接状态发生变化时调用
- * @param initial_read_changed 首读资格发生变化时调用,参数表示当前是否已完成首读
- * @param cache_updated 任一轮询块成功更新 PLC 缓存后调用
- * @param error_reported 发生通信错误时调用,参数为 UTF-8 可读错误文本
- */
- virtual void setCallbacks(
- std::function<void()> state_changed,
- std::function<void(bool)> initial_read_changed,
- std::function<void()> cache_updated,
- std::function<void(const std::string &)> error_reported) = 0;
- };
|