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 | |