| 1 | // Copyright (C) 2019 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <private/opcuaendpointdiscovery_p.h> |
| 5 | #include <private/opcuaconnection_p.h> |
| 6 | |
| 7 | QT_BEGIN_NAMESPACE |
| 8 | |
| 9 | /*! |
| 10 | \qmltype EndpointDiscovery |
| 11 | \inqmlmodule QtOpcUa |
| 12 | \brief Provides information about available endpoints on a server. |
| 13 | \since QtOpcUa 5.13 |
| 14 | \deprecated [6.9] |
| 15 | |
| 16 | Allows to fetch and access information about available endpoints on a server. |
| 17 | |
| 18 | \snippet ../../src/declarative_opcua/doc/snippets/basic/basic.qml Basic discovery |
| 19 | */ |
| 20 | |
| 21 | /*! |
| 22 | \qmlproperty string EndpointDiscovery::serverUrl |
| 23 | |
| 24 | Discovery URL of the server to fetch the endpoints from. |
| 25 | Every time the URL is changed, a request to the given server is started. |
| 26 | |
| 27 | When starting the request, the list of available endpoints is cleared |
| 28 | and the status is set to \l {Status::Status}{Status.GoodCompletesAsynchronously}. Once the request is finished, |
| 29 | \l status changes. |
| 30 | |
| 31 | Make sure to check \l status before accessing the list of endpoints. |
| 32 | |
| 33 | \sa endpointsChanged |
| 34 | */ |
| 35 | |
| 36 | /*! |
| 37 | \qmlproperty int EndpointDiscovery::count |
| 38 | |
| 39 | Current number of endpoints in this element. |
| 40 | Before using any data from an endpoint discovery, you should check \l status if the information |
| 41 | was successfully retrieved. |
| 42 | |
| 43 | \sa status Status |
| 44 | */ |
| 45 | |
| 46 | /*! |
| 47 | \qmlproperty Status EndpointDiscovery::status |
| 48 | |
| 49 | The current status of this element. |
| 50 | In case the last retrieval of endpoints was successful, the status |
| 51 | is \l {Status::Status}{Status.Good}. |
| 52 | |
| 53 | \code |
| 54 | if (endpoints.status.isGood) { |
| 55 | // do something |
| 56 | } else { |
| 57 | // handle error |
| 58 | } |
| 59 | \endcode |
| 60 | |
| 61 | \sa Status |
| 62 | */ |
| 63 | |
| 64 | /*! |
| 65 | \qmlsignal EndpointDiscovery::endpointsChanged() |
| 66 | |
| 67 | Emitted when a retrieval request started, finished or failed. |
| 68 | In a called function, you should first check the \l status of the object. |
| 69 | If the status is \l {Status::Status}{Status.GoodCompletesAsynchronously}, |
| 70 | the request is still running. |
| 71 | If the status is \l {Status::Status}{Status.Good}, the request has finished |
| 72 | and the endpoint descriptions can be read. If the status is not good, an |
| 73 | error happened and \l status contains the returned error code. |
| 74 | |
| 75 | \code |
| 76 | onEndpointsChanged: { |
| 77 | if (endpoints.status.isGood) { |
| 78 | if (endpoints.status.status == QtOpcua.Status.GoodCompletesAsynchronusly) |
| 79 | return; // wait until finished |
| 80 | if (endpoints.count > 0) { |
| 81 | var endpointUrl = endpoints.at(0).endpointUrl(); |
| 82 | console.log(endpointUrl); |
| 83 | } |
| 84 | } else { |
| 85 | // handle error |
| 86 | } |
| 87 | } |
| 88 | \endcode |
| 89 | |
| 90 | \sa status count at Status EndpointDescription |
| 91 | */ |
| 92 | |
| 93 | /*! |
| 94 | \qmlproperty Connection EndpointDiscovery::connection |
| 95 | |
| 96 | The connection to be used for requesting information. |
| 97 | |
| 98 | If this property is not set, the default connection will be used, if any. |
| 99 | |
| 100 | \sa Connection, Connection::defaultConnection |
| 101 | */ |
| 102 | |
| 103 | OpcUaEndpointDiscovery::OpcUaEndpointDiscovery(QObject *parent) |
| 104 | : QObject(parent) |
| 105 | { |
| 106 | connect(sender: this, signal: &OpcUaEndpointDiscovery::serverUrlChanged, context: this, slot: &OpcUaEndpointDiscovery::startRequestEndpoints); |
| 107 | connect(sender: this, signal: &OpcUaEndpointDiscovery::connectionChanged, context: this, slot: &OpcUaEndpointDiscovery::startRequestEndpoints); |
| 108 | } |
| 109 | |
| 110 | OpcUaEndpointDiscovery::~OpcUaEndpointDiscovery() = default; |
| 111 | |
| 112 | const QString &OpcUaEndpointDiscovery::serverUrl() const |
| 113 | { |
| 114 | return m_serverUrl; |
| 115 | } |
| 116 | |
| 117 | void OpcUaEndpointDiscovery::setServerUrl(const QString &serverUrl) |
| 118 | { |
| 119 | if (serverUrl == m_serverUrl) |
| 120 | return; |
| 121 | |
| 122 | m_serverUrl = serverUrl; |
| 123 | emit serverUrlChanged(serverUrl: m_serverUrl); |
| 124 | } |
| 125 | |
| 126 | int OpcUaEndpointDiscovery::count() const |
| 127 | { |
| 128 | return m_endpoints.size(); |
| 129 | } |
| 130 | |
| 131 | /*! |
| 132 | \qmlmethod EndpointDescription EndpointDiscovery::at(index) |
| 133 | |
| 134 | Returns the endpoint description at given \a index. |
| 135 | In case there are no endpoints available or the index is invalid, an invalid |
| 136 | endpoint description is returned. |
| 137 | Before using any data from this, you should check \l status if retrieval of the |
| 138 | information was successful. |
| 139 | |
| 140 | \code |
| 141 | if (endpoints.status.isGood) { |
| 142 | if (endpoints.count > 0) |
| 143 | var endpointUrl = endpoints.at(0).endpointUrl(); |
| 144 | console.log(endpointUrl); |
| 145 | } else { |
| 146 | // handle error |
| 147 | } |
| 148 | \endcode |
| 149 | |
| 150 | \sa count status EndpointDescription |
| 151 | */ |
| 152 | |
| 153 | QOpcUaEndpointDescription OpcUaEndpointDiscovery::at(int row) const |
| 154 | { |
| 155 | if (row >= m_endpoints.size()) |
| 156 | return QOpcUaEndpointDescription(); |
| 157 | return m_endpoints.at(i: row); |
| 158 | } |
| 159 | |
| 160 | const OpcUaStatus &OpcUaEndpointDiscovery::status() const |
| 161 | { |
| 162 | return m_status; |
| 163 | } |
| 164 | |
| 165 | void OpcUaEndpointDiscovery::connectSignals() |
| 166 | { |
| 167 | auto conn = connection(); |
| 168 | |
| 169 | if (!conn || !conn->m_client) |
| 170 | return; |
| 171 | connect(sender: conn->m_client, signal: &QOpcUaClient::endpointsRequestFinished, context: this, slot: &OpcUaEndpointDiscovery::handleEndpoints, type: Qt::UniqueConnection); |
| 172 | } |
| 173 | |
| 174 | void OpcUaEndpointDiscovery::handleEndpoints(const QList<QOpcUaEndpointDescription> &endpoints, QOpcUa::UaStatusCode statusCode, const QUrl &requestUrl) |
| 175 | { |
| 176 | if (requestUrl != m_serverUrl) |
| 177 | return; // response is not for last request |
| 178 | |
| 179 | m_status = OpcUaStatus(statusCode); |
| 180 | |
| 181 | if (m_status.isBad()) { |
| 182 | emit statusChanged(); |
| 183 | return; |
| 184 | } |
| 185 | |
| 186 | m_endpoints = endpoints; |
| 187 | emit endpointsChanged(); |
| 188 | emit countChanged(); |
| 189 | emit statusChanged(); |
| 190 | } |
| 191 | |
| 192 | void OpcUaEndpointDiscovery::startRequestEndpoints() |
| 193 | { |
| 194 | if (!m_componentCompleted) |
| 195 | return; |
| 196 | |
| 197 | if (m_serverUrl.isEmpty()) |
| 198 | return; |
| 199 | |
| 200 | m_endpoints.clear(); |
| 201 | |
| 202 | if (!m_connection) { |
| 203 | // If there is not connection set, try the default connection |
| 204 | // Any connection change will restart this function |
| 205 | connection(); |
| 206 | return; |
| 207 | } |
| 208 | |
| 209 | auto conn = connection(); |
| 210 | |
| 211 | if (!conn || !conn->m_client) { |
| 212 | m_status = OpcUaStatus(QOpcUa::BadNotConnected); |
| 213 | } else if (m_serverUrl.isEmpty()) { |
| 214 | m_status = OpcUaStatus(QOpcUa::BadInvalidArgument); |
| 215 | } else { |
| 216 | m_status = OpcUaStatus(QOpcUa::GoodCompletesAsynchronously); |
| 217 | conn->m_client->requestEndpoints(url: m_serverUrl); |
| 218 | } |
| 219 | |
| 220 | emit endpointsChanged(); |
| 221 | emit statusChanged(); |
| 222 | } |
| 223 | |
| 224 | void OpcUaEndpointDiscovery::setConnection(OpcUaConnection *connection) |
| 225 | { |
| 226 | if (connection == m_connection || !connection) |
| 227 | return; |
| 228 | |
| 229 | if (m_connection) |
| 230 | disconnect(sender: m_connection, signal: &OpcUaConnection::backendChanged, receiver: this, slot: &OpcUaEndpointDiscovery::connectSignals); |
| 231 | |
| 232 | m_connection = connection; |
| 233 | |
| 234 | connect(sender: m_connection, signal: &OpcUaConnection::backendChanged, context: this, slot: &OpcUaEndpointDiscovery::connectSignals, type: Qt::UniqueConnection); |
| 235 | connectSignals(); |
| 236 | emit connectionChanged(connection); |
| 237 | } |
| 238 | |
| 239 | OpcUaConnection *OpcUaEndpointDiscovery::connection() |
| 240 | { |
| 241 | if (!m_connection) |
| 242 | setConnection(OpcUaConnection::defaultConnection()); |
| 243 | |
| 244 | return m_connection; |
| 245 | } |
| 246 | |
| 247 | void OpcUaEndpointDiscovery::classBegin() |
| 248 | { |
| 249 | } |
| 250 | |
| 251 | void OpcUaEndpointDiscovery::componentComplete() |
| 252 | { |
| 253 | m_componentCompleted = true; |
| 254 | startRequestEndpoints(); |
| 255 | } |
| 256 | |
| 257 | QT_END_NAMESPACE |
| 258 | |