#ifndef PLC_MODBUS_CLIENT_H #define PLC_MODBUS_CLIENT_H #include #include #include #include #include #include #include "IYModbusTCPClient.h" /** * @brief PLC Modbus 通信客户端(纯 std::thread 实现,无 Qt 依赖) * * 协议流程(0-based 地址): * 1. 轮询读取地址0(触发寄存器),边沿 0→1 时触发工作 * 2. 触发后写 0 到地址0(清除触发) * 3. 写 0 到地址1(工作状态=正在工作) * 4. 工作完成,输出坐标数据到地址2开始 * 5. 写 1 到地址1(工作状态=完成正常),或写 2(错误) * * 线程模型: * - 单一轮询线程:连接检测、自动重连、寄存器读取 * - 回调在轮询线程执行,调用方需注意线程安全 * * 锁策略: * - m_mutex: 保护 m_plcClient * - m_callbackMutex: 保护回调函数指针 * - 回调通知时先复制回调再释放锁,避免死锁 */ class PLCModbusClient { public: // 寄存器地址默认值(0-based 协议) static constexpr int DEFAULT_ADDR_TRIGGER = 0; // 触发寄存器 static constexpr int DEFAULT_ADDR_WORK_STATUS = 1; // 工作状态寄存器 static constexpr int DEFAULT_ADDR_COORD_DATA_START = 2; // 坐标数据起始 // 工作状态值枚举 enum WorkStatusValue { WORK_STATUS_WORKING = 0, // 正在工作 WORK_STATUS_COMPLETE_OK = 1, // 完成正常 WORK_STATUS_ERROR = 2 // 错误 }; // 数据字节序枚举 enum ByteOrder { BIG_ENDIAN_ORDER = 0, // 大端序 (ABCD) - 高字节在前 LITTLE_ENDIAN_ORDER = 1 // 小端序 (DCBA) - 低字节在前 }; struct RegisterConfig { int addrTrigger = 0; // 触发寄存器地址 int addrWorkStatus = 1; // 工作状态寄存器地址 int addrCoordDataStart = 2; // 坐标数据起始地址 int byteOrder = BIG_ENDIAN_ORDER; // 数据字节序,默认大端序 }; struct CoordinateData { float x = 0.0f; float y = 0.0f; float z = 0.0f; float roll = 0.0f; float pitch = 0.0f; float yaw = 0.0f; }; static constexpr int REGS_PER_POINT = 12; static constexpr int MAX_POINTS = 10; // 回调类型 using PhotoTriggerCallback = std::function; using ConnectionStateCallback = std::function; using ErrorCallback = std::function; using ReconnectingCallback = std::function; public: PLCModbusClient(); ~PLCModbusClient(); // 禁止拷贝和移动 PLCModbusClient(const PLCModbusClient&) = delete; PLCModbusClient& operator=(const PLCModbusClient&) = delete; PLCModbusClient(PLCModbusClient&&) = delete; PLCModbusClient& operator=(PLCModbusClient&&) = delete; // ========== 生命周期 ========== bool Initialize(const std::string& plcIP, int plcPort = 502); bool Initialize(const std::string& plcIP, int plcPort, const RegisterConfig& regConfig); void Shutdown(); void StartPolling(int intervalMs = 100); void StopPolling(); // ========== 回调设置 ========== void SetPhotoTriggerCallback(PhotoTriggerCallback callback); void SetConnectionStateCallback(ConnectionStateCallback callback); void SetErrorCallback(ErrorCallback callback); void SetReconnectingCallback(ReconnectingCallback callback); // ========== 配置 ========== void SetReconnectInterval(int intervalMs); void SetAutoReconnect(bool enable); // ========== PLC 操作 ========== bool SendCoordinateToPLC(const CoordinateData& coord, int pointIndex = 0); bool WriteWorkStatus(WorkStatusValue status); bool ClearTrigger(); // ========== 轮询控制 ========== void PauseTriggerPolling(); // 暂停读取触发寄存器(检测期间调用) void ResumeTriggerPolling(); // 恢复读取触发寄存器(检测完成后调用) bool IsTriggerPollingPaused() const; // ========== 状态查询 ========== bool IsPLCConnected() const; private: // 轮询线程 void pollThreadFunc(); // 连接管理 bool checkConnection(); bool tryConnectPLC(); void disconnectPLC(); // 主动断开连接(用于触发重连) // 寄存器操作 int readTriggerRegister(); void floatToRegisters(float value, uint16_t& high, uint16_t& low); // 安全的回调通知(先复制回调,释放锁后再调用) void notifyConnectionStateChanged(bool connected); void notifyError(const std::string& errorMsg); void notifyTriggerRequested(int cameraIndex); void notifyReconnecting(const std::string& device, int attempt); private: // PLC 客户端 IYModbusTCPClient* m_plcClient; // 回调函数 PhotoTriggerCallback m_photoCallback; ConnectionStateCallback m_connectionStateCallback; ErrorCallback m_errorCallback; ReconnectingCallback m_reconnectingCallback; // 连接配置 std::string m_plcIP; int m_plcPort; RegisterConfig m_registerConfig; // 状态 bool m_lastTriggerState; bool m_lastConnectedState; // 线程控制 std::thread m_pollThread; std::atomic m_pollRunning; std::atomic m_triggerPollingPaused; // 暂停触发寄存器轮询标志 int m_pollIntervalMs; // 重连控制 std::atomic m_shutdownRequested; std::atomic m_autoReconnect; int m_reconnectInterval; int m_plcReconnectAttempts; // 互斥锁 mutable std::mutex m_mutex; std::mutex m_callbackMutex; }; #endif // PLC_MODBUS_CLIENT_H