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

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