#pragma once #include "services/plc_communication_gateway.h" #include "domain/register_repository.h" #include #include #include #include #include #include #include class PlcRegisterRepository; class QModbusReply; class QModbusRtuSerialMaster; struct PlcCommunicationFailure; struct PlcPollBlock { RegisterArea area = RegisterArea::M; int startAddress = 0; int count = 1; }; // 基于 Qt Modbus RTU 的异步 PLC 通信实现 // 负责连接、轮询、单点写入、故障恢复和首读资格,不阻塞 UI 线程 class PlcCommunicationService final : public QObject, public PlcCommunicationGateway { Q_OBJECT public: // 创建通信服务,并把 PLC 缓存仓库交给它管理 explicit PlcCommunicationService( PlcRegisterRepository &repository, QObject *parent = nullptr); // 释放 Modbus 主站和未完成的通信资源 ~PlcCommunicationService() override; // 按给定串口参数连接 PLC,并开始异步轮询 PlcCommunicationResult connectDevice( const PlcSerialConfiguration &configuration) override; // 停止轮询并断开 PLC void disconnectDevice() override; // 设置需要周期性读取的 M/D 地址集合 PlcCommunicationResult setPollAddresses( const std::vector &addresses) override; PlcCommunicationResult setPollAddresses( const std::vector &addresses, const std::vector &multi_word_ranges) override; // 返回当前连接状态 PlcConnectionState state() const override; // 判断本次连接是否已经完成所有轮询块的首次读取 bool initialReadCompleted() const override; // 返回最近一次通信错误的类型 PlcCommunicationError lastErrorType() const override; // 返回最近一次通信错误的文字 const std::string &lastError() const override; // 注册状态、首读、缓存更新和错误通知回调 void setCallbacks( std::function state_changed, std::function initial_read_changed, std::function cache_updated, std::function poll_cycle_completed, std::function error_reported) override; signals: // 连接状态发生变化 void stateChanged(); // 首次完整读取资格发生变化 void initialReadCompletedChanged(bool completed); // PLC 缓存收到新的成功读数 void cacheUpdated(); // 全部轮询块成功更新一轮 void pollCycleCompleted(); // 通信错误的可读提示文字 void communicationError(const QString &message); private: // 把去重后的地址集合压缩为有限数量的连续读块 void rebuildPollBlocks(); // 应用新的轮询集合;有请求在途时由 pending_* 延后应用 void applyPollAddresses(const std::vector &addresses); // 发送下一段异步读取请求 void pollNextBlock(); // Faulted 状态下用轻量探测判断通信是否恢复 void probeRecovery(); // 处理恢复探测失败,并安排下一次探测 void handleRecoveryProbeFailure(QModbusDevice::Error error); // 探测成功后恢复轮询,但仍需重新完成首读 void restoreCommunication(); // 处理读回复并把值写入 PLC 缓存 void handleReadFinished(QModbusReply *reply, PlcPollBlock block); // 发送单点 M/D 写请求;缓存等待后续轮询确认 RegisterWriteResult sendBitWrite(const RegisterAddress &address, bool value); RegisterWriteResult sendWordWrite( const RegisterAddress &address, std::int16_t value); RegisterWriteResult sendWordsWrite( const RegisterAddress &address, const std::vector &values); // 更新“所有轮询块均成功读取”的真机进入资格 void updateInitialReadCompleted(bool completed, bool force_notification = false); // 处理没有主动断开时发生的串口断线 void handleUnexpectedDisconnect(); // 把 Qt Modbus 错误转换成项目自己的通信错误 void handleModbusError(QModbusDevice::Error error); // 关闭串口并清理当前连接的请求和状态 void closeSerialSession(); // 修改连接状态并通知外部观察者 void setState(PlcConnectionState state); // 保存错误信息、更新状态并通知外部观察者 void setError(const PlcCommunicationFailure &failure); PlcRegisterRepository &repository_; // 用于保存 PLC 最近一次成功读回的 M/D 值 std::unique_ptr master_; // Qt Modbus RTU 主站对象 QTimer poll_timer_; // 周期性触发下一轮轮询 QTimer recovery_timer_; // Faulted 状态下定时发起恢复探测 PlcSerialConfiguration configuration_; // 当前串口和站号配置 std::vector poll_addresses_; // 当前生效的轮询地址 std::vector pending_poll_addresses_; // 请求在途时暂存的新地址 std::vector poll_multi_word_ranges_; // 当前轮询集合中的多字范围 std::vector pending_poll_multi_word_ranges_; // 请求在途时暂存的多字范围 std::vector poll_blocks_; // 根据地址合并出的连续读块 std::size_t next_poll_block_ = 0; // 下一次要读取的读块下标 QModbusReply *pending_reply_ = nullptr; // 当前未完成的读请求或恢复探测 QModbusReply *pending_write_reply_ = nullptr; // 当前未完成的单点写请求 bool poll_update_pending_ = false; // 是否有等待请求完成后应用的新轮询集合 bool disconnecting_ = false; // 是否正在执行主动断开 bool serial_session_opened_ = false; // 本次串口会话是否曾经成功打开 bool received_valid_response_ = false; // 本次连接是否收到过有效 PLC 回复 std::uint64_t connection_generation_ = 0; // 连接代次,用于丢弃旧连接的异步回复 PlcConnectionState state_ = PlcConnectionState::Disconnected; // 当前通信状态 bool initial_read_completed_ = false; // 是否已完成首读,可作为真机运行门槛 // 首读阶段每个读块的完成标记,全部为 true 后才允许进入真机 std::vector initial_blocks_read_; PlcCommunicationError last_error_type_ = PlcCommunicationError::None; // 最近一次错误类型 std::string last_error_; // 最近一次错误的可读文字 std::function state_changed_callback_; // 状态变化时调用 std::function initial_read_changed_callback_; // 首读资格变化时调用 std::function cache_updated_callback_; // 缓存更新后调用 std::function poll_cycle_completed_callback_; // 完整轮询一轮后调用 std::function error_reported_callback_; // 发生错误时调用 };