#ifndef PLC_MODBUS_CLIENT_H #define PLC_MODBUS_CLIENT_H #include #include #include #include #include #include #include "IYModbusTCPClient.h" /** * @brief PLC Modbus communication client implemented with std::thread. * * Workflow: * 1. Poll PLC register D1000 and trigger a photo request when the value is non-zero. * 2. Clear D1000 after the request is consumed. * 3. Write pose data to D2000+ on success. * 4. Write a result status code to D1002 after processing. */ class PLCModbusClient { public: static constexpr int DEFAULT_ADDR_PHOTO_REQUEST = 1001; // D1000 static constexpr int DEFAULT_ADDR_DATA_COMPLETE = 1003; // D1002 static constexpr int DEFAULT_ADDR_COORD_DATA_START = 2001; // D2000 enum ByteOrder { BIG_ENDIAN_ORDER = 0, // ABCD LITTLE_ENDIAN_ORDER = 1 // DCBA }; struct RegisterConfig { int addrPhotoRequest = 1001; // D1000 int addrDataComplete = 1003; // D1002 int addrCoordDataStart = 2001; // D2000 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; // D1002 result status codes. static constexpr int RESULT_SUCCESS = 1; static constexpr int RESULT_ABNORMAL_MATERIAL = 2; static constexpr int RESULT_NO_PRODUCT = 3; static constexpr int RESULT_ALGO_FAIL = 11; static constexpr int RESULT_CAMERA_FAIL = 12; 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); bool SendCoordinateToPLC(const CoordinateData& coord, int pointIndex = 0); bool NotifyDataComplete(int statusCode = RESULT_SUCCESS); bool ClearPhotoRequest(); void PausePhotoRequestPolling(); void ResumePhotoRequestPolling(); bool IsPhotoRequestPollingPaused() const; bool IsPLCConnected() const; private: void pollThreadFunc(); bool checkConnection(); bool tryConnectPLC(); void disconnectPLC(); int readPhotoRequest(); void floatToRegisters(float value, uint16_t& high, uint16_t& low); void notifyConnectionStateChanged(bool connected); void notifyError(const std::string& errorMsg); void notifyPhotoRequested(int cameraIndex); void notifyReconnecting(const std::string& device, int attempt); private: 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_lastPhotoRequestState; bool m_lastConnectedState; std::thread m_pollThread; std::atomic m_pollRunning; std::atomic m_photoRequestPaused; 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