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
6QT_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
104OpcUaServerDiscovery::OpcUaServerDiscovery(QObject *parent)
105 : QStandardItemModel(parent)
106{
107 insertColumn(acolumn: 0);
108}
109
110OpcUaServerDiscovery::~OpcUaServerDiscovery() = default;
111
112const QString &OpcUaServerDiscovery::discoveryUrl() const
113{
114 return m_discoveryUrl;
115}
116
117void 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
127int 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
165QOpcUaApplicationDescription OpcUaServerDiscovery::at(int row) const
166{
167 return index(row, column: 0).data(arole: Qt::UserRole).value<QOpcUaApplicationDescription>();
168}
169
170const OpcUaStatus &OpcUaServerDiscovery::status() const
171{
172 return m_status;
173}
174
175void 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
184void 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
208void 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
236void OpcUaServerDiscovery::clearData()
237{
238 QStandardItemModel::removeRows(row: 0, count: QStandardItemModel::rowCount());
239}
240
241void 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
256OpcUaConnection *OpcUaServerDiscovery::connection()
257{
258 if (!m_connection)
259 setConnection(OpcUaConnection::defaultConnection());
260
261 return m_connection;
262}
263
264QT_END_NAMESPACE
265

source code of qtopcua/src/declarative_opcua/opcuaserverdiscovery.cpp