QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSReceiverConnectionPolicy.cc
Go to the documentation of this file.
2
3#include <utility>
4
5#include <QtCore/QStringList>
6
7#include "GPSReceiverConfig.h"
9#include "RuntimeScheduler.h"
10#ifndef QGC_NO_SERIAL_LINK
11#include "SerialPortManager.h"
12#endif
13
14QGC_LOGGING_CATEGORY(GPSReceiverConnectionPolicyLog, "GPS.Receiver.GPSReceiverConnectionPolicy")
15
17 const GPSReceiverConfiguration& configuration,
18 RuntimeScheduler* scheduler)
19 : _receiver(receiver)
20 , _configuration(configuration)
21 , _scheduler(scheduler)
22{}
23
25{
26 const bool connectionChanged = !previous.sameConnection(_configuration);
27 const bool turnedOn = !previous.autoConnect && _configuration.autoConnect;
28 const bool turnedOff = previous.autoConnect && !_configuration.autoConnect;
29 if (connectionChanged || turnedOff) {
30 _cancelRetries();
31 }
32 if (turnedOn) {
33 _paused = false;
34 _startPending = true;
35 // A saved receiver the user already connected is kept connected from now on.
36 _keepConnected = _owner == Owner::Saved && !connectionChanged;
37 }
38}
39
40void GPSReceiverConnectionPolicy::_cancelRetries()
41{
42 if (_keepConnected) {
43 qCDebug(GPSReceiverConnectionPolicyLog) << "Automatic reconnect cancelled";
44 }
45 // Nothing reconnects any more, so a message about the pending reconnect would be stale.
46 if ((_keepConnected || _retryDeadlineUs) && !_receiver.hasReceiver()) {
47 _receiver.setConnectionError({});
48 }
49 _keepConnected = false;
50 _waitingForPort = false;
51 _autoPort.clear();
52#ifndef QGC_NO_SERIAL_LINK
53 _waitingPorts.clear();
54#endif
55 _retryDeadlineUs.reset();
56 _retryBackoff.reset();
57}
58
60{
61 _cancelRetries();
62 _owner = Owner::None;
63 _startPending = false;
64}
65
66bool GPSReceiverConnectionPolicy::connectConfigured(bool allowPersistentChanges)
67{
68 // A rejected request leaves the current connection's ownership and retries as they are.
69 if (_receiver.hasReceiver()) {
70 _receiver.setConnectionError(tr("Disconnect the current receiver before connecting another."));
71 return false;
72 }
73 reset();
74 _paused = false;
75 if (!_connectConfigured(allowPersistentChanges)) {
76 return false;
77 }
78 _owner = Owner::Saved;
79 return true;
80}
81
83{
84 reset();
85 _paused = true;
86 _receiver.endConnection(true);
87}
88
90{
91 if (_owner == Owner::Saved && _configuration.autoConnect && !std::exchange(_keepConnected, true)) {
92 qCDebug(GPSReceiverConnectionPolicyLog) << "Automatic reconnect armed for the saved receiver";
93 }
94 _waitingForPort = false;
95 _retryDeadlineUs.reset();
96 _retryBackoff.reset();
97}
98
100{
101 if (_owner == Owner::Discovered && _discoveryEnabled()) {
102 if (portRemoved) {
103 // Discovery connects the receiver again when it returns.
104 _owner = Owner::None;
105 _cancelRetries();
107 }
108 _scheduleRetry();
110 }
111 if (_owner == Owner::Saved && _keepConnected) {
112 if (portRemoved) {
113 _waitingForPort = true;
115 }
116 _scheduleRetry();
118 }
119 _owner = Owner::None;
120 _cancelRetries();
122}
123
124void GPSReceiverConnectionPolicy::_scheduleRetry()
125{
126 const auto delay = _retryBackoff.next();
127 qCDebug(GPSReceiverConnectionPolicyLog) << "Retrying the receiver connection in" << delay.count() << "ms";
128 _retryDeadlineUs = _scheduler->nowUs() + static_cast<quint64>(std::chrono::microseconds(delay).count());
129}
130
132{
133 if (!_configuration.autoConnect || _paused || _receiver.hasReceiver()) {
134 return;
135 }
136 if (std::exchange(_startPending, false) && _hasSavedReceiver()) {
137 _owner = Owner::Saved;
138 _keepConnected = true;
139 }
140 if (_keepConnected) {
141 _updateSaved();
142 } else if (_discoveryEnabled()) {
143 _updateDiscovery();
144 }
145}
146
147bool GPSReceiverConnectionPolicy::_hasSavedReceiver() const
148{
149 switch (_configuration.effectiveConnectionType()) {
151 return !_configuration.serialDevice.trimmed().isEmpty();
152 case RTKSettings::Tcp:
153 return !_configuration.tcpHost.trimmed().isEmpty() && _configuration.tcpPort != 0;
154 case RTKSettings::Udp:
155 // UDP only receives, so it cannot carry a configured base.
156 return _configuration.receiverRole != RTKSettings::ConfiguredBase;
157 }
158 return false;
159}
160
161bool GPSReceiverConnectionPolicy::_discoveryEnabled() const
162{
163#ifdef QGC_NO_SERIAL_LINK
164 return false;
165#else
166 // Discovery configures known base receivers, so it never replaces a saved, network or passive receiver.
167 return _configuration.autoConnect && !_paused && _configuration.receiverRole == RTKSettings::ConfiguredBase &&
168 _configuration.effectiveConnectionType() == RTKSettings::Serial &&
169 _configuration.serialDevice.trimmed().isEmpty();
170#endif
171}
172
173void GPSReceiverConnectionPolicy::_updateSaved()
174{
175 if (_configuration.effectiveConnectionType() == RTKSettings::Serial) {
176 if (!_serialDeviceAvailable(_configuration.serialDevice.trimmed())) {
177 if (!std::exchange(_waitingForPort, true)) {
178 //: %1 is a serial device, such as /dev/ttyACM0 or COM3
179 _receiver.setConnectionError(
180 tr("Waiting for the receiver on %1.").arg(_configuration.serialDevice.trimmed()));
181 }
182 return;
183 }
184 if (std::exchange(_waitingForPort, false)) {
185 // Retry as soon as the receiver returns instead of waiting out the backoff.
186 _attemptSaved();
187 return;
188 }
189 }
190 if (!_retryDeadlineUs || _scheduler->nowUs() >= *_retryDeadlineUs) {
191 _attemptSaved();
192 }
193}
194
195void GPSReceiverConnectionPolicy::_attemptSaved()
196{
197 _retryDeadlineUs.reset();
198 // Flash-save consent is one-use and never passed by automatic attempts.
199 if (!_connectConfigured(false)) {
200 _scheduleRetry();
201 }
202}
203
204bool GPSReceiverConnectionPolicy::_serialDeviceAvailable([[maybe_unused]] const QString& device) const
205{
206#ifdef QGC_NO_SERIAL_LINK
207 return false;
208#else
209 return _receiver.serialPortAvailable(device);
210#endif
211}
212
213bool GPSReceiverConnectionPolicy::_connectConfigured(bool allowPersistentChanges)
214{
215 const bool configuredBase = _configuration.receiverRole == RTKSettings::ConfiguredBase;
216 const auto type = configuredBase ? gpsReceiverTypeForManufacturer(_configuration.baseReceiverManufacturer)
217 : std::optional(GPSType::passive);
218 if (!type) {
219 _receiver.setConnectionError(tr("Select a receiver type before connecting."));
220 return false;
221 }
222 const auto connection = _configuration.effectiveConnectionType();
223 if (connection == RTKSettings::Tcp) {
224 const QString host = _configuration.tcpHost.trimmed();
225 const uint tcpPort = _configuration.tcpPort;
226 if (host.isEmpty() || tcpPort == 0 || tcpPort > 65535) {
227 _receiver.setConnectionError(tr("Enter the receiver's TCP host and port."));
228 return false;
229 }
230 return _receiver.connectTcp(host, static_cast<quint16>(tcpPort), *type, allowPersistentChanges);
231 }
232 if (connection == RTKSettings::Udp) {
233 const uint udpPort = _configuration.udpPort;
234 if (configuredBase) {
235 _receiver.setConnectionError(
236 tr("A configured base needs a serial or TCP connection. UDP only receives data."));
237 return false;
238 }
239 if (udpPort == 0 || udpPort > 65535) {
240 _receiver.setConnectionError(tr("Enter the UDP port that receives the data."));
241 return false;
242 }
243 return _receiver.connectUdp(static_cast<quint16>(udpPort), *type);
244 }
245#ifndef QGC_NO_SERIAL_LINK
246 const QString device = _configuration.serialDevice.trimmed();
247 if (!_serialDeviceAvailable(device)) {
248 _receiver.setConnectionError(tr("Select an available serial device that is not a bootloader."));
249 return false;
250 }
251 // Zero asks configurable receivers to detect the rate.
252 const auto baud = _configuration.serialBaudRate;
253 if (!gpsValidBaudRate(baud, *type == GPSType::passive)) {
255 return false;
256 }
257 auto claim = _receiver.claimSerialPort(device);
258 if (!claim) {
259 _receiver.setConnectionError(tr("The selected serial device is already in use."));
260 return false;
261 }
262 return _receiver.connectSerial(device, *type, baud, allowPersistentChanges, std::move(claim));
263#else
264 return false;
265#endif
266}
267
268void GPSReceiverConnectionPolicy::_updateDiscovery()
269{
270#ifndef QGC_NO_SERIAL_LINK
271 if (_owner == Owner::Discovered) {
272 if (_serialDeviceAvailable(_autoPort)) {
273 if (!_retryDeadlineUs || _scheduler->nowUs() >= *_retryDeadlineUs) {
274 _connectDiscovered(_autoPort);
275 }
276 return;
277 }
278 // The receiver being retried was unplugged; discovery finds it again when it returns.
279 _owner = Owner::None;
280 _cancelRetries();
281 }
282 const QStringList receivers = _receiver.rtkReceiverPorts();
283 _waitingPorts.removeIf([&receivers](const QString& device) { return !receivers.contains(device); });
284 const quint64 nowUs = _scheduler->nowUs();
285 for (const QString& device : receivers) {
286 if (_waitingPorts.settled(device, nowUs)) {
287 _connectDiscovered(device);
288 return;
289 }
290 }
291#endif
292}
293
294#ifndef QGC_NO_SERIAL_LINK
295void GPSReceiverConnectionPolicy::_connectDiscovered(const QString& device)
296{
297 auto claim = _receiver.claimSerialPort(device);
298 if (!claim) {
299 return;
300 }
301 _owner = Owner::Discovered;
302 _autoPort = device;
303 _waitingPorts.clear();
304 _retryDeadlineUs.reset();
305 // The USB identity only chose the port; the saved manufacturer chooses the family, detecting it when Automatic.
306 const auto type = gpsReceiverTypeForManufacturer(_configuration.baseReceiverManufacturer);
307 if (!type) {
308 _receiver.setConnectionError(tr("Select a receiver type before connecting."));
309 }
310 if (!type || !_receiver.connectSerial(device, *type, 0, false, std::move(claim))) {
311 _scheduleRetry();
312 }
313}
314#endif
QString gpsReceiverConfigErrorText(GPSReceiverConfigError error)
Translated diagnostic; empty for GPSReceiverConfigError::None.
constexpr bool gpsValidBaudRate(uint32_t baudRate, bool passive)
GPSReceiverSessionOutcome
What the policy does after a receiver session ends; the receiver words the message for it.
@ Unplugged
A discovered receiver was unplugged; discovery connects it again when it returns.
@ None
Nothing reconnects; the receiver's own message for the error applies.
@ AutoRetrying
A discovered receiver failed or was lost; discovery retries it.
@ WaitingForPort
The saved receiver was unplugged; it reconnects when its port returns.
@ Retrying
The saved receiver's connection was lost; a retry is scheduled.
std::optional< GPSType > gpsReceiverTypeForManufacturer(int manufacturer)
The family a persisted manufacturer selects, GPSType::automatic for GPS_AUTOMATIC_MANUFACTURER; empty...
#define QGC_LOGGING_CATEGORY(name, categoryStr)
constexpr Delay next(Delay retryAfter=Delay::zero())
Returns peek(retryAfter) and counts the attempt.
constexpr void reset()
void disconnectConfigured()
Disconnects at the user's request and pauses automatic connection.
GPSReceiverSessionOutcome sessionEnded(bool portRemoved)
void reset()
Forgets connection ownership and pending retries, before another connection replaces it.
void update()
Periodic tick: due automatic connections and USB discovery.
void configurationChanged(const GPSReceiverConfiguration &previous)
The receiver's configuration changed from previous.
bool connectConfigured(bool allowPersistentChanges)
Connects the saved receiver at the user's request and resumes automatic connection.
virtual QStringList rtkReceiverPorts() const =0
virtual GPSSerialClaim claimSerialPort(const QString &device)=0
Claims the device until the last copy is released; empty when it is in use.
virtual bool hasReceiver() const =0
virtual bool serialPortAvailable(const QString &device) const =0
The device is plugged in and not in its bootloader.
virtual void setConnectionError(const QString &message)=0
virtual bool connectSerial(const QString &device, GPSType type, uint32_t baudRate, bool allowPersistentChanges, GPSSerialClaim claim)=0
virtual void endConnection(bool clearError)=0
virtual bool connectUdp(quint16 port, GPSType type)=0
virtual bool connectTcp(const QString &host, quint16 port, GPSType type, bool allowPersistentChanges)=0
virtual quint64 nowUs() const =0
void removeIf(Predicate forget)
Forgets the ports forget returns true for.
bool settled(const QString &port, quint64 nowUs)
Records port as listed at nowUs; true once it has been listed for the settle delay.
uint64_t nowUs()
The receiver configuration the application's settings binding supplies as values.
RTKSettings::ReceiverRole receiverRole
Only a configured base is written to by QGroundControl.
bool autoConnect
Keep the saved receiver connected, or discover a configured base on USB when no device is saved.
bool sameConnection(const GPSReceiverConfiguration &other) const
RTKSettings::ConnectionType effectiveConnectionType() const
The connection this configuration uses; builds without serial links connect a serial selection over T...
int baseReceiverManufacturer
A GPSReceiverDescriptor manufacturer, or GPS_AUTOMATIC_MANUFACTURER to detect the family on every con...