QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSDeviceTransport.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <limits>
5
6#include <QtCore/QEventLoop>
7#include <QtCore/QIODevice>
8#include <QtCore/QTimer>
9#include <QtNetwork/QAbstractSocket>
10#ifndef QGC_NO_SERIAL_LINK
11#ifdef Q_OS_ANDROID
12#include "qserialport.h"
13#else
14#include <QtSerialPort/QSerialPort>
15#endif
16#endif
17
18GPSReadResult GPSDeviceTransport::read(std::span<uint8_t> buffer, std::chrono::milliseconds timeout)
19{
20 if (isCancelled()) {
22 }
23 if (readUnavailable()) {
24 return readFailure();
25 }
26 if (buffer.empty()) {
28 }
29 if (!waitReadable(QDeadlineTimer((std::max) (timeout, std::chrono::milliseconds::zero()), Qt::PreciseTimer))) {
30 if (isCancelled()) {
32 }
34 }
35 const qint64 count = take(buffer);
36 if (count < 0) {
38 }
39 return {GPSReadStatus::Data, static_cast<int>(count)};
40}
41
43{
44 return !device();
45}
46
47bool GPSDeviceTransport::waitReadable(QDeadlineTimer deadline)
48{
49 QIODevice* const io = device();
50 // bytesAvailable() only sees the device's buffer, so an immediate poll also services input not taken in yet.
51 if (deadline.hasExpired() && io->bytesAvailable() == 0 && !fatalError()) {
52 io->waitForReadyRead(0);
53 }
54 return io->bytesAvailable() > 0 || waitForDevice([io]() { return io->bytesAvailable() > 0; }, deadline);
55}
56
57qint64 GPSDeviceTransport::take(std::span<uint8_t> buffer)
58{
59 return device()->read(reinterpret_cast<char*>(buffer.data()), static_cast<qint64>(buffer.size()));
60}
61
63{
64 return {GPSReadStatus::Closed, 0, device() ? device()->errorString() : QString()};
65}
66
67GPSWriteResult GPSDeviceTransport::writeData(QByteArrayView bytes, QDeadlineTimer deadline)
68{
69 QIODevice* const io = device();
70 if (fatalError() || !io || io->bytesToWrite() != 0) {
72 }
73 qint64 drained = 0;
74 const QMetaObject::Connection counter = QObject::connect(
75 io, &QIODevice::bytesWritten, io, [&drained](qint64 count) { drained += count; }, Qt::DirectConnection);
76 const int length = static_cast<int>(bytes.size());
77 int accepted = 0;
79 while (accepted < length || io->bytesToWrite() > 0) {
80 if (isCancelled() || fatalError() || deadline.hasExpired()) {
81 break;
82 }
83 if (accepted < length && io->bytesToWrite() < WRITE_BUFFER_BYTES) {
84 const qint64 count =
85 io->write(bytes.data() + accepted,
86 (std::min) (qint64(length - accepted), WRITE_BUFFER_BYTES - io->bytesToWrite()));
87 if (count < 0) {
88 status = GPSWriteStatus::Error;
89 break;
90 }
91 accepted += static_cast<int>(count);
92 }
93 if (io->bytesToWrite() > 0) {
94 waitWritten(deadline);
95 }
96 }
97 QObject::disconnect(counter);
98 const int written = static_cast<int>(std::clamp(confirmedWritten(accepted, drained), qint64(0), qint64(accepted)));
99 // A write confirmed in full succeeded, even if the deadline, a stop or a link failure followed it.
100 if (written < length) {
101 if (isCancelled()) {
103 } else if (fatalError()) {
104 status = GPSWriteStatus::Error;
105 } else if (deadline.hasExpired()) {
107 }
108 }
109 const GPSWriteResult result{status, accepted, written,
110 status == GPSWriteStatus::Error ? readFailure().detail : QString()};
111 if (status != GPSWriteStatus::Completed && accepted > 0) {
112 retire();
113 }
114 return result;
115}
116
117void GPSDeviceTransport::waitWritten(QDeadlineTimer& deadline)
118{
119 QIODevice* const io = device();
120 (void) waitForDevice([io]() { return io->bytesToWrite() == 0; }, deadline);
121}
122
123qint64 GPSDeviceTransport::confirmedWritten(int accepted, qint64 drained)
124{
125 Q_UNUSED(accepted)
126 return drained;
127}
128
130{
131 device()->close();
132}
133
134bool GPSDeviceTransport::waitUntil(const std::function<bool()>& done, QDeadlineTimer deadline)
135{
136 if (!isCancelled() && !done() && !deadline.hasExpired()) {
137 QEventLoop loop;
138 QTimer timeout;
139 const auto check = [&]() {
140 if (isCancelled() || deadline.hasExpired() || done()) {
141 loop.quit();
142 }
143 };
144 if (QIODevice* const io = device()) {
145 (void) QObject::connect(io, &QIODevice::readyRead, &loop, check);
146 (void) QObject::connect(io, &QIODevice::bytesWritten, &loop, check);
147 if (auto* const socket = qobject_cast<QAbstractSocket*>(io)) {
148 (void) QObject::connect(socket, &QAbstractSocket::errorOccurred, &loop, check);
149 (void) QObject::connect(socket, &QAbstractSocket::stateChanged, &loop, check);
150 }
151#ifndef QGC_NO_SERIAL_LINK
152 if (auto* const port = qobject_cast<QSerialPort*>(io)) {
153 (void) QObject::connect(port, &QSerialPort::errorOccurred, &loop, check);
154 }
155#endif
156 }
157 (void) QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
158 timeout.setSingleShot(true);
159 timeout.setTimerType(Qt::PreciseTimer);
160 if (!deadline.isForever()) {
161 timeout.start(
162 static_cast<int>((std::min) (deadline.remainingTime(), qint64((std::numeric_limits<int>::max)()))));
163 }
164 // cancel() runs this on the cancelling thread, so it only posts. Declared after the loop so it is destroyed
165 // first: the destructor waits for a running invocation, and the loop's destructor discards an unhandled post.
166 const GPSCancelCallback wake(
167 cancelToken(), [&loop]() { QMetaObject::invokeMethod(&loop, &QEventLoop::quit, Qt::QueuedConnection); });
168 loop.exec();
169 }
170 return !isCancelled() && done();
171}
172
173bool GPSDeviceTransport::waitForDevice(const std::function<bool()>& ready, QDeadlineTimer deadline)
174{
175 if (!device() || fatalError()) {
176 return false;
177 }
178 (void) waitUntil([&]() { return fatalError() || ready(); }, deadline);
179 return !isCancelled() && !fatalError() && ready();
180}
GPSWriteStatus
virtual qint64 take(std::span< uint8_t > buffer)
Takes buffered input.
virtual void retire()
Retires the connection after a failed write that accepted bytes; by default closes the device.
GPSWriteResult writeData(QByteArrayView bytes, QDeadlineTimer deadline) override
Receives non-empty bytes and an unexpired, capped deadline that implementations must honor.
virtual GPSReadResult readFailure() const
Describes a failed link; by default, a closed connection with the device's error.
bool waitUntil(const std::function< bool()> &done, QDeadlineTimer deadline)
bool waitForDevice(const std::function< bool()> &ready, QDeadlineTimer deadline)
GPSReadResult read(std::span< uint8_t > buffer, std::chrono::milliseconds timeout) override
virtual QIODevice * device() const =0
The open device, or null before open().
virtual void waitWritten(QDeadlineTimer &deadline)
Waits for queued bytes to drain. May expire deadline to end the write.
virtual bool readUnavailable() const
Whether read() fails without waiting; by default, when there is no device.
virtual bool waitReadable(QDeadlineTimer deadline)
Waits until input is buffered. Input reported buffered is delivered even after the link fails.
virtual qint64 confirmedWritten(int accepted, qint64 drained)
virtual bool fatalError() const =0
bool isCancelled() const
static constexpr qint64 WRITE_BUFFER_BYTES
Bytes a link queues for writing at once.
const GPSCancelToken & cancelToken() const
void errorOccurred(QSerialPort::SerialPortError error)