QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PassiveProtocol.cc
Go to the documentation of this file.
1#include <array>
2#include <chrono>
3#include <memory>
4#include <optional>
5#include <utility>
6
7#include "GPSCommandChannel.h"
8#include "GPSFamilyProtocol.h"
9#include "GPSNMEAStream.h"
10#include "GPSProtocolRuntime.h"
11#include "GPSReceiverFamilies.h"
12#include "MonotonicClock.h"
13#include "NMEASentence.h"
14#include "QGCLoggingCategory.h"
15#include "SBF/SBFBlocks.h"
16
17QGC_LOGGING_CATEGORY(PassiveProtocolLog, "GPS.Protocols.Passive")
18
19namespace {
20
22class NMEAProtocol final : public GPSFamilyProtocol
23{
24public:
25 bool configure(GPSCommandChannel&, GPSConfig, unsigned&) override { return false; }
26
27 GPSReceiveUpdates onFrame(const GPSFrame& frame, GPSDecodeContext& context) override
28 {
29 return _stream.onFrame(frame, context);
30 }
31
32 void flush(GPSDecodeContext& context) override { _stream.flush(context); }
33
34 [[nodiscard]] const GPSNMEAStream& stream() const { return _stream; }
35
36private:
37 GPSNMEAStream _stream;
38};
39
40const GPSReceiverFamily NMEA_FAMILY{
41 .type = GPSType::passive,
42 .logCategory = &PassiveProtocolLog,
43 .stream = {.framers = GPSFrameKind::ASCIILine, .enabled = GPSFrameKind::ASCIILine},
44 .create = &gpsCreateProtocol<NMEAProtocol>,
45};
46
47bool standardSentence(const GPSFrame& frame)
48{
49 return NMEA::isStandardSentence(frame.text());
50}
51
52bool checkedBlock(const GPSFrame& frame)
53{
54 return SBF::checkedHeader(frame.bytes).has_value();
55}
56
58struct PassiveInput
59{
60 const GPSReceiverFamily* family;
61 GPSFrameKinds frames;
63 bool (*accepts)(const GPSFrame& frame) = nullptr;
64};
65
67constexpr std::array INPUTS{
68 PassiveInput{&NMEA_FAMILY, GPSFrameKind::ASCIILine, &standardSentence},
69 PassiveInput{&UBX::FAMILY, GPSFrameKind::UBX},
70 PassiveInput{&SBF::FAMILY, GPSFrameKind::SBF, &checkedBlock},
71};
72static_assert(INPUTS.front().family == &NMEA_FAMILY);
73
75constexpr GPSFrameKinds inputFrames()
76{
77 GPSFrameKinds frames = GPSFrameKind::RTCM3;
78 for (const auto& input : INPUTS) {
79 frames |= input.frames;
80 }
81 return frames;
82}
83
88class PassiveProtocol final : public GPSFamilyProtocol
89{
90public:
91 static constexpr std::chrono::milliseconds SOURCE_TIMEOUT{3000};
92
93 PassiveProtocol() { _reset(); }
94
95 bool configure(GPSCommandChannel& channel, GPSConfig, unsigned& baud) override
96 {
97 _ready = false;
98 _reset();
99 channel.stream().reset();
100 if (!channel.setBaudrate(baud)) {
101 channel.failControl(QStringLiteral("The link cannot run at %1 baud").arg(baud));
102 return false;
103 }
104 _ready = true;
105 return true;
106 }
107
108 GPSReceiveUpdates onFrame(const GPSFrame& frame, GPSDecodeContext& context) override
109 {
110 _context = &context;
111 if (frame.kind == GPSFrameKind::RTCM3) {
112 context.publishRTCM(frame.bytes);
113 return {};
114 }
115 const auto input = _inputOf(frame);
116 if (!input) {
117 return {};
118 }
119 if (!_followed) {
120 _follow(*input);
121 }
122 (void) _inputs[*input].runtime->consumeFrame(frame);
123 return {};
124 }
125
126 void flush(GPSDecodeContext& context) override
127 {
128 _context = &context;
129 for (const auto& input : _inputs) {
130 (void) input.runtime->consume({});
131 }
132 }
133
134 bool receiverReady() const override { return _ready; }
135
136 GPSReceiveUpdates receive(GPSCommandChannel& channel, std::chrono::milliseconds timeout) override
137 {
138 const auto& nmea = static_cast<const NMEAProtocol&>(_inputs.front().runtime->protocol()).stream();
139 return nmea.receive(channel, timeout);
140 }
141
142private:
143 struct Input
144 {
145 std::unique_ptr<GPSProtocolRuntime> runtime;
146 uint64_t lastPositionUs = 0;
147 };
148
149 static std::optional<size_t> _inputOf(const GPSFrame& frame)
150 {
151 for (size_t index = 0; index < INPUTS.size(); ++index) {
152 const auto& input = INPUTS[index];
153 if (input.frames.testFlag(frame.kind) && (!input.accepts || input.accepts(frame))) {
154 return index;
155 }
156 }
157 return std::nullopt;
158 }
159
160 void _reset()
161 {
162 _followed.reset();
163 for (size_t index = 0; index < INPUTS.size(); ++index) {
164 GPSRuntimeIO io;
165 io.clock.nowUs = [this] { return _context ? _context->nowUs() : 0; };
166 GPSRuntimeObserver observer;
167 observer.decoded = [this, index](const GPSEventBatch& batch) { _received(index, batch); };
168 _inputs[index] = {.runtime = std::make_unique<GPSProtocolRuntime>(*INPUTS[index].family, std::move(io),
169 std::move(observer)),
170 .lastPositionUs = 0};
171 _inputs[index].runtime->armNavigationDecode();
172 }
173 }
174
175 void _received(size_t index, const GPSEventBatch& batch)
176 {
177 if (batch.updates.testFlag(GPSReceiveUpdate::Position)) {
178 const uint64_t now = _context->nowUs();
179 _inputs[index].lastPositionUs = now;
180 if (!_followed || (*_followed != index &&
181 !MonotonicClock::fresh(_inputs[*_followed].lastPositionUs, now, SOURCE_TIMEOUT))) {
182 _follow(index);
183 }
184 }
185 if (_followed != index) {
186 return;
187 }
188 for (const auto& event : batch.events) {
189 _context->publishAsReceived(event);
190 }
191 _context->markUpdates(batch.updates);
192 }
193
194 void _follow(size_t index)
195 {
196 _followed = index;
197 // The NMEA decoder's family is passive, which reports standard NMEA.
198 const GPSType family = INPUTS[index].family->type;
199 qCDebug(PassiveProtocolLog) << "Passive input decodes" << family;
200 _context->publishAsReceived(GPSInputProtocol{family});
201 }
202
203 std::array<Input, INPUTS.size()> _inputs;
204 std::optional<size_t> _followed;
205 GPSDecodeContext* _context = nullptr;
206 bool _ready = false;
207};
208
209} // namespace
210
211namespace Passive {
212
214 .type = GPSType::passive,
215 .logCategory = &PassiveProtocolLog,
216 .stream = {.framers = inputFrames(), .enabled = inputFrames(), .shareSBFSync = true},
217 .create = &gpsCreateProtocol<PassiveProtocol>,
218};
219
220} // namespace Passive
static std::atomic< jobject > _context
@ 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 setBaudrate(unsigned baudrate)
void failControl(GPSProtocolError failure=GPSProtocolError::Protocol)
GPSStreamDemux & stream()
void publishRTCM(std::span< const uint8_t > frame)
virtual GPSReceiveUpdates onFrame(const GPSFrame &frame, GPSDecodeContext &context)=0
virtual bool configure(GPSCommandChannel &channel, GPSConfig config, unsigned &baud)=0
virtual void flush(GPSDecodeContext &context)
Publishes time-driven output before each decode, such as expired epochs.
constexpr bool fresh(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds lifetime)
Whether a receipt is younger than lifetime.
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)
const GPSReceiverFamily FAMILY
const GPSReceiverFamily FAMILY
std::optional< BlockHeader > checkedHeader(std::span< const uint8_t > block)
The header of a block candidate whose length fits the candidate and whose CRC matches; nullopt otherw...
Definition SBFBlocks.h:97
const GPSReceiverFamily FAMILY
std::function< uint64_t()> nowUs
Definition GPSClock.h:12
The receiver configuration requested for one configure() attempt.
Events decoded from one span of received bytes, in publication order.
GPSReceiveUpdates updates
GPSClock clock
Without a wait, the runtime sleeps in short slices until the duration elapses or cancelToken is cance...
Observers of runtime output, invoked synchronously on the runtime thread.
std::function< void(const GPSEventBatch &)> decoded
Borrowed for the callback; it may decode nested input through GPSProtocolRuntime::consume().