| 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/opcuaserverdiscovery_p.h> |
| 5 | |
| 6 | QT_BEGIN_NAMESPACE |
| 7 | |
| 8 | /*! |
| 9 | \qmltype ServerDiscovery |
| 10 | \inqmlmodule QtOpcUa |
| 11 | \brief Provides information about available servers. |
| 12 | \since QtOpcUa 5.13 |
| 13 | |
| 14 | Allows to fetch and access information about servers known to a server or discovery server. |
| 15 | |
| 16 | \snippet ../../src/declarative_opcua/doc/snippets/basic/basic.qml Basic discovery |
| 17 | */ |
| 18 | |
| 19 | /*! |
| 20 | \qmlproperty string ServerDiscovery::discoveryUrl |
| 21 | |
| 22 | URL of the server to retrieve the list of servers from. |
| 23 | Every time the URL is changed, a request to the given server is started. |
| 24 | |
| 25 | When starting the request, the list of available servers is cleared |
| 26 | and the status is set to \l {Status::Status}{Status.GoodCompletesAsynchronously}. |
| 27 | Once the request is finished, \l status changes. |
| 28 | Make sure to check the \l status before accessing the list of servers. |
| 29 | |
| 30 | \code |
| 31 | onServersChanged: { |
| 32 | if (status.isGood) { |
| 33 | if (status.status == QtOpcUa.Status.GoodCompletesAsynchronusly) |
| 34 | return; // wait until finished |
| 35 | if (count > 0) { |
| 36 | var serverUrl = at(0).serverUrl(); |
| 37 | console.log(serverUrl); |
| 38 | } |
| 39 | } else { |
| 40 | // handle error |
| 41 | } |
| 42 | } |
| 43 | \endcode |
| 44 | |
| 45 | \sa ApplicationDescription status at count Status |
| 46 | */ |
| 47 | |
| 48 | /*! |
| 49 | \qmlproperty int ServerDiscovery::count |
| 50 | |
| 51 | Current number of servers in this element. |
| 52 | Before using any data from this server discovery, you should check \l status if retrieval of the |
| 53 | information was successful. |
| 54 | |
| 55 | \sa status Status |
| 56 | */ |
| 57 | |
| 58 | /*! |
| 59 | \qmlproperty Status ServerDiscovery::status |
| 60 | |
| 61 | The current status of this element. |
| 62 | In case the last retrieval of servers was successful, the status |
| 63 | should be \c Status.Good. |
| 64 | |
| 65 | \code |
| 66 | if (status.isGood) { |
| 67 | // Choose endpoint to connect to |
| 68 | } else { |
| 69 | // handle error |
| 70 | } |
| 71 | \endcode |
| 72 | |
| 73 | \sa Status |
| 74 | */ |
| 75 | |
| 76 | /*! |
| 77 | \qmlsignal SeverDiscovery::serversChanged() |
| 78 | |
| 79 | Emitted when a retrieval request started, finished or failed. |
| 80 | In a called function, you should first the the \l status of the object. |
| 81 | In case the status is \l Status.GoodCompletesAsynchronously, the request is still running. |
| 82 | In case the status is \l Status.Good, the request has finished and the application descriptions |
| 83 | can be read. In case the status is not good, an error happened and \l status contains the |
| 84 | returned error code. |
| 85 | |
| 86 | \code |
| 87 | onServersChanged: { |
| 88 | if (status.isGood) { |
| 89 | if (status.status == QtOpcUa.Status.GoodCompletesAsynchronusly) |
| 90 | return; // wait until finished |
| 91 | if (count > 0) { |
| 92 | var serverUrl = at(0).endpointUrl(); |
| 93 | console.log(serverUrl); |
| 94 | } |
| 95 | } else { |
| 96 | // handle error |
| 97 | } |
| 98 | } |
| 99 | \endcode |
| 100 | |
| 101 | \sa status count at QtOpcUa.Status ApplicationDescription |
| 102 | */ |
| 103 | |
| 104 | OpcUaServerDiscovery::OpcUaServerDiscovery(QObject *parent) |
| 105 | : QStandardItemModel(parent) |
| 106 | { |
| 107 | insertColumn(acolumn: 0); |
| 108 | } |
| 109 | |
| 110 | OpcUaServerDiscovery::~OpcUaServerDiscovery() = default; |
| 111 | |
| 112 | const QString &OpcUaServerDiscovery::discoveryUrl() const |
| 113 | { |
| 114 | return m_discoveryUrl; |
| 115 | } |
| 116 | |
| 117 | void OpcUaServerDiscovery::setDiscoveryUrl(const QString &discoveryUrl) |
| 118 | { |
| 119 | if (discoveryUrl == m_discoveryUrl) |
| 120 | return; |
| 121 | m_discoveryUrl = discoveryUrl; |
| 122 | |
| 123 | startFindServers(); |
| 124 | emit discoveryUrlChanged(); |
| 125 | } |
| 126 | |
| 127 | int OpcUaServerDiscovery::count() const |
| 128 | { |
| 129 | return rowCount(); |
| 130 | } |
| 131 | |
| 132 | /*! |
| 133 | \qmlmethod ApplicationDescription ServerDiscovery::at(index) |
| 134 | |
| 135 | Returns the application description at given \a index. |
| 136 | In case there are no servers available or the index is invalid, an invalid |
| 137 | application description is returned. |
| 138 | Before using any returned data, you should check \l status if retrieval of the |
| 139 | information was successful. |
| 140 | |
| 141 | \code |
| 142 | if (servers.status.isGood) { |
| 143 | if (servers.count > 0) |
| 144 | var serverUrl = at(0).serverUrl(); |
| 145 | console.log(serverUrl); |
| 146 | // Choose endpoint to connect to |
| 147 | } else { |
| 148 | // handle error |
| 149 | } |
| 150 | \endcode |
| 151 | |
| 152 | \sa count status ApplicationDescription |
| 153 | */ |
| 154 | |
| 155 | /*! |
| 156 | \qmlproperty Connection ServerDiscovery::connection |
| 157 | |
| 158 | The connection to be used for requesting information. |
| 159 | |
| 160 | If this property is not set, the default connection will be used, if any. |
| 161 | |
| 162 | \sa Connection, Connection::defaultConnection |
| 163 | */ |
| 164 | |
| 165 | QOpcUaApplicationDescription OpcUaServerDiscovery::at(int row) const |
| 166 | { |
| 167 | return index(row, column: 0).data(arole: Qt::UserRole).value<QOpcUaApplicationDescription>(); |
| 168 | } |
| 169 | |
| 170 | const OpcUaStatus &OpcUaServerDiscovery::status() const |
| 171 | { |
| 172 | return m_status; |
| 173 | } |
| 174 | |
| 175 | void OpcUaServerDiscovery::connectSignals() |
| 176 | { |
| 177 | auto conn = connection(); |
| 178 | if (!conn || !conn->m_client) |
| 179 | return; |
| 180 | connect(sender: conn->m_client, signal: &QOpcUaClient::findServersFinished, context: this, slot: &OpcUaServerDiscovery::handleServers, type: Qt::UniqueConnection); |
| 181 | startFindServers(); |
| 182 | } |
| 183 | |
| 184 | void OpcUaServerDiscovery::handleServers(const QList<QOpcUaApplicationDescription> &servers, QOpcUa::UaStatusCode statusCode, const QUrl &requestUrl) |
| 185 | { |
| 186 | if (requestUrl != m_discoveryUrl) |
| 187 | return; // response is not for last request |
| 188 | |
| 189 | m_status = OpcUaStatus(statusCode); |
| 190 | |
| 191 | if (m_status.isBad()) { |
| 192 | emit statusChanged(); |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | clearData(); |
| 197 | for (const auto &i : std::as_const(t: servers)) { |
| 198 | const int newRow = QStandardItemModel::rowCount(); |
| 199 | QStandardItemModel::insertRow(arow: newRow); |
| 200 | QStandardItemModel::setData(index: index(row: newRow, column: 0), value: QString(i.applicationUri() + u'\n' + i.productUri()), role: Qt::DisplayRole); |
| 201 | QStandardItemModel::setData(index: index(row: newRow, column: 0), value: QVariant::fromValue(value: i), role: Qt::UserRole); |
| 202 | } |
| 203 | emit countChanged(); |
| 204 | emit serversChanged(); |
| 205 | emit statusChanged(); |
| 206 | } |
| 207 | |
| 208 | void OpcUaServerDiscovery::startFindServers() |
| 209 | { |
| 210 | if (m_discoveryUrl.isEmpty()) |
| 211 | return; |
| 212 | |
| 213 | if (!m_connection) { |
| 214 | // In case no connection is set the default connection will be |
| 215 | // used, which fill trigger this function afterwards. |
| 216 | connection(); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | clearData(); |
| 221 | |
| 222 | auto conn = connection(); |
| 223 | if (!conn || !conn->m_client) { |
| 224 | m_status = OpcUaStatus(QOpcUa::BadNotConnected); |
| 225 | } else if (m_discoveryUrl.isEmpty()) { |
| 226 | m_status = OpcUaStatus(QOpcUa::BadInvalidArgument); |
| 227 | } else { |
| 228 | m_status = OpcUaStatus(QOpcUa::GoodCompletesAsynchronously); |
| 229 | conn->m_client->findServers(url: m_discoveryUrl); |
| 230 | } |
| 231 | |
| 232 | emit serversChanged(); |
| 233 | emit statusChanged(); |
| 234 | } |
| 235 | |
| 236 | void OpcUaServerDiscovery::clearData() |
| 237 | { |
| 238 | QStandardItemModel::removeRows(row: 0, count: QStandardItemModel::rowCount()); |
| 239 | } |
| 240 | |
| 241 | void OpcUaServerDiscovery::setConnection(OpcUaConnection *connection) |
| 242 | { |
| 243 | if (connection == m_connection || !connection) |
| 244 | return; |
| 245 | |
| 246 | if (m_connection) |
| 247 | disconnect(sender: m_connection, signal: &OpcUaConnection::backendChanged, receiver: this, slot: &OpcUaServerDiscovery::connectSignals); |
| 248 | |
| 249 | m_connection = connection; |
| 250 | |
| 251 | connect(sender: m_connection, signal: &OpcUaConnection::backendChanged, context: this, slot: &OpcUaServerDiscovery::connectSignals); |
| 252 | connectSignals(); |
| 253 | emit connectionChanged(connection); |
| 254 | } |
| 255 | |
| 256 | OpcUaConnection *OpcUaServerDiscovery::connection() |
| 257 | { |
| 258 | if (!m_connection) |
| 259 | setConnection(OpcUaConnection::defaultConnection()); |
| 260 | |
| 261 | return m_connection; |
| 262 | } |
| 263 | |
| 264 | QT_END_NAMESPACE |
| 265 | |