/** * @file BobinkClient.h * @brief QML singleton managing the OPC UA connection lifecycle. * * Wraps QOpcUaClient into a declarative interface: LDS discovery, * endpoint selection, PKI, and certificate trust flow. * Single connection at a time (app-wide singleton). */ #ifndef BOBINKCLIENT_H #define BOBINKCLIENT_H #include #include #include #include #include #include #include #include #include #include class BobinkAuth; class BobinkClient : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY (bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY (QString serverUrl READ serverUrl WRITE setServerUrl NOTIFY serverUrlChanged) Q_PROPERTY (BobinkAuth *auth READ auth WRITE setAuth NOTIFY authChanged) Q_PROPERTY (QString discoveryUrl READ discoveryUrl WRITE setDiscoveryUrl NOTIFY discoveryUrlChanged) Q_PROPERTY (bool discovering READ discovering NOTIFY discoveringChanged) Q_PROPERTY (QVariantList servers READ servers NOTIFY serversChanged) Q_PROPERTY (QString pkiDir READ pkiDir WRITE setPkiDir NOTIFY pkiDirChanged) Q_PROPERTY ( QString certFile READ certFile WRITE setCertFile NOTIFY certFileChanged) Q_PROPERTY ( QString keyFile READ keyFile WRITE setKeyFile NOTIFY keyFileChanged) public: explicit BobinkClient (QObject *parent = nullptr); ~BobinkClient () override; static BobinkClient *instance (); /** * @brief QML singleton factory. * @note CppOwnership — lives for the process lifetime. */ static BobinkClient *create (QQmlEngine *qmlEngine, QJSEngine *jsEngine); bool connected () const; QString serverUrl () const; void setServerUrl (const QString &url); BobinkAuth *auth () const; void setAuth (BobinkAuth *auth); QOpcUaClient *opcuaClient () const; QString discoveryUrl () const; void setDiscoveryUrl (const QString &url); bool discovering () const; const QList &discoveredServers () const; QVariantList servers () const; QString pkiDir () const; void setPkiDir (const QString &path); QString certFile () const; void setCertFile (const QString &path); QString keyFile () const; void setKeyFile (const QString &path); enum SecurityMode { SignAndEncrypt = 3, Sign = 2, None = 1, }; Q_ENUM (SecurityMode) enum SecurityPolicy { Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss, }; Q_ENUM (SecurityPolicy) /** @brief Discover endpoints, pick the most secure, connect. */ Q_INVOKABLE void connectToServer (); /** @brief Connect directly without endpoint discovery. */ Q_INVOKABLE void connectDirect (SecurityPolicy policy, SecurityMode mode); Q_INVOKABLE void disconnectFromServer (); /** @brief Accept the pending server certificate. */ Q_INVOKABLE void acceptCertificate (); /** @brief Reject the pending server certificate. */ Q_INVOKABLE void rejectCertificate (); Q_INVOKABLE void startDiscovery (); Q_INVOKABLE void stopDiscovery (); /** @brief Auto-detect cert/key from the PKI directory and apply. */ Q_INVOKABLE void autoDetectPki (); /** @brief Apply PKI dirs and cert/key. Call before connecting. */ Q_INVOKABLE void applyPki (); signals: void connectedChanged (); void serverUrlChanged (); void authChanged (); /** * @brief Emitted when the server presents an untrusted cert. * * The connection blocks until acceptCertificate() or * rejectCertificate() is called (30 s timeout, auto-rejects). */ void certificateTrustRequested (const QString &certInfo); void connectionError (const QString &message); void statusMessage (const QString &message); void discoveryUrlChanged (); void discoveringChanged (); void serversChanged (); void pkiDirChanged (); void certFileChanged (); void keyFileChanged (); private slots: void handleStateChanged (QOpcUaClient::ClientState state); void handleEndpointsReceived (const QList &endpoints, QOpcUa::UaStatusCode statusCode, const QUrl &requestUrl); void handleConnectError (QOpcUaErrorState *errorState); void handleFindServersFinished ( const QList &servers, QOpcUa::UaStatusCode statusCode, const QUrl &requestUrl); void doDiscovery (); private: void setupClient (); static BobinkClient *s_instance; QOpcUaProvider *m_provider = nullptr; QOpcUaClient *m_client = nullptr; BobinkAuth *m_auth = nullptr; QString m_serverUrl; bool m_connected = false; // Certificate trust event loop — see handleConnectError(). QEventLoop *m_certLoop = nullptr; bool m_certAccepted = false; QString m_discoveryUrl; int m_discoveryInterval = 30000; // ms QTimer m_discoveryTimer; bool m_discovering = false; QList m_discoveredServers; QString m_pkiDir; QString m_certFile; QString m_keyFile; QVariantList m_serversCache; }; #endif // BOBINKCLIENT_H