QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GPSReceiverDetector.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <utility>
5
6#include "GPSDeadline.h"
9#include "NMEASentence.h"
10#include "QGCLoggingCategory.h"
11
12QGC_LOGGING_CATEGORY(GPSReceiverDetectorLog, "GPS.Protocols.Detector")
13
14namespace {
15
16// ASCIILine frames '$' and '#' lines, NMEA sentences included; NMEASentence would only shadow it. SBF shares the '$'
17// sync, so text replies still reach the line framer.
18constexpr GPSStreamConfig ALL_FRAMERS{
21 .shareSBFSync = true,
22};
23
24bool supportsRate(const GPSReceiverFamily& family, unsigned baud)
25{
26 return std::ranges::find(family.baudCandidates, baud) != family.baudCandidates.end();
27}
28
29} // namespace
30
31GPSReceiverSignatures::GPSReceiverSignatures(std::span<const GPSReceiverFamily* const> families)
32 : _families(families)
33 , _demux(ALL_FRAMERS)
34{}
35
36void GPSReceiverSignatures::push(std::span<const uint8_t> bytes)
37{
38 for (const uint8_t byte : bytes) {
39 _demux.push(byte, *this);
40 }
41}
42
44{
45 _demux.reset();
46 _match.reset();
47 _framedTraffic = false;
48}
49
50void GPSReceiverSignatures::frame(const GPSFrame& frame)
51{
52 if (!_match) {
53 for (const auto* family : _families) {
54 const QLatin1StringView evidence = family->signature ? family->signature(frame) : QLatin1StringView();
55 if (!evidence.isEmpty()) {
56 _match = Match{.family = family, .evidence = evidence};
57 break;
58 }
59 }
60 }
61 _framedTraffic |= frame.kind == GPSFrameKind::RTCM3 ||
63}
64
65GPSReceiverDetector::GPSReceiverDetector(std::span<const GPSReceiverFamily* const> families, GPSRuntimeIO io,
66 std::function<void(const GPSConfigurationEvidence&)> commandFinished)
67 : _families(families)
68 , _signatures(families)
69 , _io(std::move(io))
70 , _commandFinished(std::move(commandFinished))
71{
72 // Every byte read while detecting, probe replies included, is checked for signatures.
73 _io.read = [this, read = std::move(_io.read)](std::span<uint8_t> buffer, GPSDeadline deadline) {
74 const GPSReadResult result = read ? read(buffer, deadline) : GPSReadResult{GPSReadStatus::Error};
75 if (result.status == GPSReadStatus::Data && result.bytesRead > 0 &&
76 static_cast<size_t>(result.bytesRead) <= buffer.size()) {
77 _signatures.push(buffer.first(static_cast<size_t>(result.bytesRead)));
78 }
79 return result;
80 };
81}
82
83std::vector<unsigned> GPSReceiverDetector::baudCandidates(std::span<const GPSReceiverFamily* const> families)
84{
85 std::vector<unsigned> listed;
86 for (const auto* family : families) {
87 listed.insert(listed.end(), family->baudCandidates.begin(), family->baudCandidates.end());
88 }
89 std::ranges::sort(listed);
90 listed.erase(std::ranges::unique(listed).begin(), listed.end());
91 std::vector<unsigned> rates;
92 for (const unsigned rate : BAUD_PRIORITY) {
93 if (std::ranges::binary_search(listed, rate)) {
94 rates.push_back(rate);
95 }
96 }
97 for (const unsigned rate : listed) {
98 if (std::ranges::find(BAUD_PRIORITY, rate) == BAUD_PRIORITY.end()) {
99 rates.push_back(rate);
100 }
101 }
102 return rates;
103}
104
106{
107 const std::vector<unsigned> rates = baud ? std::vector<unsigned>{baud} : baudCandidates(_families);
108 const uint64_t untilUs = GPSDeadline::after(_io.clock.nowUs(), TIMEOUT).untilUs;
110 std::vector<unsigned> tried;
111 for (const unsigned rate : rates) {
112 if (_stopped(result)) {
113 return result;
114 }
115 if (_io.clock.nowUs() >= untilUs) {
116 break;
117 }
118 const GPSBaudStatus status = _io.setBaudrate ? _io.setBaudrate(rate) : GPSBaudStatus::Unsupported;
119 if (status == GPSBaudStatus::Unsupported) {
120 qCDebug(GPSReceiverDetectorLog) << "Link cannot run at" << rate << "baud; skipped";
121 continue;
122 }
123 if (status != GPSBaudStatus::Configured) {
124 result.error =
126 result.errorDetail = status == GPSBaudStatus::Cancelled
127 ? QString()
128 : QStringLiteral("Cannot set the receiver link to %1 baud").arg(rate);
129 return result;
130 }
131 tried.push_back(rate);
132 _signatures.reset();
133 switch (_listen(rate, untilUs, result)) {
134 case Listen::Failed:
135 return result;
136 case Listen::Identified:
137 return _identified(*_signatures.match()->family, rate, _signatures.match()->evidence);
138 case Listen::Quiet:
139 break;
140 }
141 for (const auto* family : _families) {
142 if (family->baudCandidates.empty() || (!baud && !supportsRate(*family, rate))) {
143 continue;
144 }
145 const uint64_t now = _io.clock.nowUs();
146 if (now >= untilUs) {
147 break;
148 }
149 qCDebug(GPSReceiverDetectorLog)
150 << "Probing" << gpsReceiverName(family->type) << "identity at" << rate << "baud";
151 GPSProtocolRuntime runtime(*family, _io, {.commandFinished = _commandFinished});
152 const bool answered = runtime.probe(GPSDeadline{untilUs}.remaining(now));
153 if (runtime.error() == GPSProtocolError::Cancelled || runtime.error() == GPSProtocolError::Transport) {
154 result.error = runtime.error();
155 result.errorDetail = runtime.errorDetail();
156 return result;
157 }
158 if (answered) {
159 return _identified(*family, rate, QStringLiteral("identity query"));
160 }
161 if (const auto& match = _signatures.match()) {
162 return _identified(*match->family, rate, match->evidence);
163 }
164 }
165 if (_signatures.framedTraffic()) {
167 result.errorDetail = QStringLiteral(
168 "The receiver sends standard NMEA or RTCM at %1 baud, but no supported "
169 "receiver family answered its identification query")
170 .arg(rate);
171 qCDebug(GPSReceiverDetectorLog).noquote() << result.errorDetail;
172 return result;
173 }
174 }
175 if (_stopped(result)) {
176 return result;
177 }
179 if (tried.empty()) {
180 result.errorDetail = QStringLiteral("The receiver link cannot run at any rate a supported receiver uses");
181 } else if (_io.clock.nowUs() >= untilUs) {
182 result.errorDetail = QStringLiteral("Receiver detection timed out after %1 s")
183 .arg(std::chrono::duration_cast<std::chrono::seconds>(TIMEOUT).count());
184 } else {
185 result.errorDetail = QStringLiteral("No supported receiver answered at %1 baud").arg(gpsRateList(tried));
186 }
187 qCDebug(GPSReceiverDetectorLog).noquote() << result.errorDetail;
188 return result;
189}
190
191GPSReceiverDetector::Listen GPSReceiverDetector::_listen(unsigned baud, uint64_t untilUs, GPSReceiverDetection& result)
192{
193 qCDebug(GPSReceiverDetectorLog) << "Listening at" << baud << "baud";
194 const uint64_t listenUntilUs = std::min(untilUs, GPSDeadline::after(_io.clock.nowUs(), LISTEN_WINDOW).untilUs);
195 std::array<uint8_t, GPSCommandChannel::READ_CHUNK_SIZE> buffer{};
196 Listen outcome = Listen::Quiet;
197 while (outcome == Listen::Quiet && !_signatures.match() && _io.clock.nowUs() < listenUntilUs) {
198 if (_stopped(result)) {
199 outcome = Listen::Failed;
200 break;
201 }
202 const GPSReadResult read = _io.read(buffer, GPSDeadline{listenUntilUs});
203 switch (read.status) {
206 break;
209 outcome = Listen::Failed;
210 break;
211 default:
213 result.errorDetail = read.detail;
214 outcome = Listen::Failed;
215 break;
216 }
217 }
218 if (outcome == Listen::Quiet && _signatures.match()) {
219 outcome = Listen::Identified;
220 }
221 return outcome;
222}
223
224bool GPSReceiverDetector::_stopped(GPSReceiverDetection& result) const
225{
226 if (result.error != GPSProtocolError::None) {
227 return true;
228 }
229 if (_io.cancelToken.isCancelled()) {
231 return true;
232 }
233 return false;
234}
235
236GPSReceiverDetection GPSReceiverDetector::_identified(const GPSReceiverFamily& family, unsigned baud,
237 QString evidence) const
238{
239 qCDebug(GPSReceiverDetectorLog).noquote() << QStringLiteral("Detected %1 receiver at %2 baud from %3")
240 .arg(gpsReceiverName(family.type))
241 .arg(baud)
242 .arg(evidence);
243 GPSReceiverDetection detection;
244 detection.family = &family;
245 detection.baud = baud;
246 detection.evidence = std::move(evidence);
247 return detection;
248}
QString gpsRateList(std::span< const unsigned > rates)
rates as a diagnostic names them, such as "38400, 9600".
GPSBaudStatus
How the link answered a baud rate change.
@ Protocol
The receiver violated or rejected the control protocol.
@ Cancelled
A stop was requested; not a receiver or link fault.
@ Transport
The link could not read, write, or change its baud rate.
QString gpsReceiverName(GPSType type)
The name of type's receiver family as shown to users; empty when no descriptor exists.
@ SBF
Septentrio "$@" block candidates, up to the first 110 bytes; the decoder checks the CRC.
@ UBX
u-blox 0xB5 0x62 frames with a valid Fletcher checksum.
@ ASCIILine
Printable lines up to CR/LF, without the terminator; the decoder checks checksums.
@ RTCM3
0xD3 frames with a valid CRC-24Q.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
bool isCancelled() const
Safe to call from any thread.
bool probe(std::chrono::milliseconds budget)
static constexpr std::chrono::milliseconds TIMEOUT
static constexpr std::chrono::milliseconds LISTEN_WINDOW
GPSReceiverDetection detect(unsigned baud=0)
Identifies the receiver at baud, which then every family is probed at, or else at each baudCandidates...
static std::vector< unsigned > baudCandidates(std::span< const GPSReceiverFamily *const > families)
The rates detection tries without a fixed rate: every candidate rate of families, in likelihood order...
static constexpr std::array< unsigned, 8 > BAUD_PRIORITY
GPSReceiverDetector(std::span< const GPSReceiverFamily *const > families, GPSRuntimeIO io, std::function< void(const GPSConfigurationEvidence &)> commandFinished={})
GPSReceiverSignatures(std::span< const GPSReceiverFamily *const > families)
void push(std::span< const uint8_t > bytes)
const std::optional< Match > & match() const
The first family signature since the last reset.
void reset()
Forgets partial frames and findings, as after a rate change.
bool framedTraffic() const
Standard NMEA sentences or RTCM3 frames arrived, so the link runs at the receiver's rate.
void push(uint8_t byte, Handler &handler)
QByteArray read(int deviceId, int length, int timeout)
bool isStandardSentence(std::string_view text)
A checked sentence from a standard talker, not a proprietary $P... sentence.
std::optional< Frame > frame(std::string_view text)
std::function< uint64_t()> nowUs
Definition GPSClock.h:12
Evidence from one configuration command, not a claim of physical receiver operation.
Definition GPSCommand.h:46
static GPSDeadline after(uint64_t nowUs, std::chrono::microseconds timeout)
A negative timeout is already due at nowUs.
Definition GPSDeadline.h:14
uint64_t untilUs
Definition GPSDeadline.h:11
std::chrono::milliseconds remaining(uint64_t nowUs) const
Rounded up to whole milliseconds and capped at INT32_MAX milliseconds.
Definition GPSDeadline.h:20
GPSReadStatus status
The receiver detection found, or why it found none.
const GPSReceiverFamily * family
Null when no family was identified.
GPSProtocolError error
Protocol when nothing was identified; Cancelled or Transport when the link ended detection.
unsigned baud
The rate the receiver answered at.
QString evidence
What identified it: a signature such as "UBX frames", or "identity query".
std::span< const unsigned > baudCandidates
std::function< GPSReadResult(std::span< uint8_t >, GPSDeadline)> read
GPSCancelToken cancelToken
Checked before each write and baud change; reads observe it in the transport.
GPSClock clock
Without a wait, the runtime sleeps in short slices until the duration elapses or cancelToken is cance...
std::function< GPSBaudStatus(unsigned)> setBaudrate
The framers of one receiver stream and how they share it.
GPSFrameKinds framers
Framers the family can use; only these can be enabled later.