QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
UBXConfigurator.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <string_view>
4#include <utility>
5#include <variant>
6
7#include <QtCore/QByteArrayView>
8
9#include "GPSCommandChannel.h"
10#include "GPSDeadline.h"
11#include "GPSReceiverFamilies.h"
12#include "LittleEndian.h"
13#include "NMEASentence.h"
14#include "QGCLoggingCategory.h"
15#include "UBX/UBXProtocol.h"
16
17QGC_LOGGING_CATEGORY(UBXProtocolLog, "GPS.Protocols.UBX")
18
19namespace Cfg = UBX::Cfg;
20namespace Msg = UBX::Msg;
21namespace Plan = UBX::Plan;
22using Plan::NakPolicy;
23
24namespace UBX {
25
26bool Protocol::_baseStationUnsupported() const
27{
28 const auto& identity = _identity;
29 // A receiver without time mode can neither survey in nor hold a fixed position.
30 if (identity.timeModeUnsupported) {
31 return true;
32 }
33 if (identity.board == UBX::Board::u_blox8) {
34 return !identity.isM8p && !identity.model.isEmpty();
35 }
36 const auto profile = UBX::receiverProfile(identity.board);
37 return !profile.rtcmOutput && profile.baseCapabilityKnown;
38}
39
40bool Protocol::configure(GPSCommandChannel& channel, GPSConfig config, unsigned& baud)
41{
42 _base = config.base;
43 _valsetAckAmbiguous = false;
44 _legacyRTCM = {};
45 _ready = false;
46 _setMode({}, channel.stream());
47 _controller = {};
48 _requests = {};
49
50 const unsigned requestedBaud = baud;
51 const auto detection = channel.detectBaud(Plan::BAUD_RATES, requestedBaud, [this, &channel]() -> GPSBaudProbe {
52 channel.stream().reset(GPSFrameKind::UBX);
53 (void) channel.receiveCycle(Plan::BAUD_PROBE_DRAIN);
55 if (channel.failed()) {
56 return GPSBaudProbe::Stop;
57 }
58 const bool identified = _identify(channel);
59 return identified ? GPSBaudProbe::Found : GPSBaudProbe::TryNext;
60 });
61 const auto& identity = _identity;
62 // Discovery only polls identity: silence or an unsupported identity must not change receiver settings.
63 if (!detection.found) {
64 channel.failNoAnswer(detection.probed);
65 return false;
66 }
67 if (identity.board == UBX::Board::unknown) {
68 QString detail = QStringLiteral("Unsupported u-blox receiver: hardware version %1")
69 .arg(QString::fromLatin1(identity.hardware));
70 if (!identity.protocolVersion.isEmpty()) {
71 detail += QStringLiteral(", protocol version %1").arg(QString::fromLatin1(identity.protocolVersion));
72 }
73 channel.failControl(detail);
74 return false;
75 }
76 if (_baseStationUnsupported()) {
77 const QString model =
78 identity.model.isEmpty() ? QStringLiteral("This u-blox receiver") : QString::fromLatin1(identity.model);
79 channel.failControl(QStringLiteral("%1 cannot run as an RTK base station").arg(model));
80 return false;
81 }
82 const unsigned desiredBaud = requestedBaud ? requestedBaud : Plan::BASE_BAUD;
83 if (!_setUpPort(channel, detection.baud, desiredBaud)) {
84 return false;
85 }
86 baud = desiredBaud;
87 _setMode({.corrections = true}, channel.stream());
88
89 bool deviceConfigured = false;
90 if (identity.protocol27) {
91 deviceConfigured = _configureDevice(channel);
92 } else {
93 deviceConfigured = _configureLegacyDevice(channel);
94 }
95 if (!deviceConfigured) {
96 return false;
97 }
98 if (!_restartSurveyIn(channel)) {
99 return false;
100 }
101 _ready = true;
102 _mode.navigation = true;
103 return true;
104}
105
106bool Protocol::_identify(GPSCommandChannel& channel)
107{
108 const auto scope = channel.deadlineScope(Plan::IDENTITY_TIMEOUT);
109 _identity.board = UBX::Board::unknown;
110 if (!_send(channel, Msg::MON_VER, {}, {{}, Plan::IDENTITY_TIMEOUT})) {
111 return false;
112 }
113 return _waitForAck(channel, Msg::MON_VER).succeeded();
114}
115
116bool Protocol::_setUpPort(GPSCommandChannel& channel, unsigned detectedBaud, unsigned desiredBaud)
117{
118 const bool modern = _identity.protocol27;
119 if (modern) {
120 if (!_transactValset(channel, Plan::uart1Protocols()).succeeded()) {
121 return false;
122 }
123 } else {
124 const auto ports = Plan::legacyPorts(detectedBaud);
125 if (!_sendAcknowledged(channel, Msg::CFG_PRT, ports)) {
126 return false;
127 }
128 }
129 if (desiredBaud == detectedBaud) {
130 return true;
131 }
132
133 const UBX::Board identifiedBoard = _identity.board;
135 if (modern) {
136 if (!_writeValset(channel, Plan::uart1Baudrate(desiredBaud))) {
137 return false;
138 }
139 } else {
140 const auto ports = Plan::legacyPorts(desiredBaud);
141 if (!_send(channel, Msg::CFG_PRT, ports, {{}, Plan::CONFIG_TIMEOUT, false})) {
142 return false;
143 }
144 }
145 const auto result = _waitForAck(channel, command);
146 const bool acknowledged = result.succeeded();
147 if (channel.failed() || result.outcome == GPSCommandOutcome::Rejected) {
148 return false;
149 }
150 if (!channel.setBaudrate(desiredBaud)) {
151 channel.failControl(QStringLiteral("The link cannot run at %1 baud").arg(desiredBaud));
152 return false;
153 }
154 if (modern && !acknowledged) {
155 // The ACK may be lost in the UART handoff; every later VALSET needs a matching readback.
156 _controller.requireConfigurationReadback(command.value());
157 }
158 channel.stream().reset(GPSFrameKind::UBX);
159 if (!_identify(channel)) {
160 return false;
161 }
162 if (_identity.board != identifiedBoard || _identity.protocol27 != modern || _controller.lateRejection()) {
163 return false;
164 }
165 if (modern && !acknowledged) {
166 return _verifyValset(channel, {"UBX-CFG-VALSET readback", Plan::CONFIG_TIMEOUT}).succeeded();
167 }
168 return true;
169}
170
171bool Protocol::_configureDevice(GPSCommandChannel& channel)
172{
173 const auto profile = UBX::receiverProfile(_identity.board);
174 const auto portsAndNavigation = Plan::portsAndNavigation(profile);
175 if (!_runPlan(channel, portsAndNavigation)) {
176 return false;
177 }
178 if (!_configureJammingDetection(channel)) {
179 return false;
180 }
181 _secSigSeen = false;
182 const auto messageOutput = Plan::messageOutput(profile);
183 return _runPlan(channel, messageOutput);
184}
185
186bool Protocol::_configureJammingDetection(GPSCommandChannel& channel)
187{
188 // Firmware with CFG-SEC-JAMDET has detection always on and no CFG-ITFM, so a rejection asks for the older monitor.
189 // An unanswered or refused probe leaves the receiver state unknown.
190 const auto probe = _transactValset(channel, Plan::jammingDetection());
191 if (probe.succeeded()) {
192 return true;
193 }
194 if (probe.outcome != GPSCommandOutcome::Rejected || _valsetsRefused()) {
195 if (!channel.failed()) {
196 qCWarning(UBXProtocolLog) << "CFG-SEC-JAMDET probe unanswered";
197 }
198 return false;
199 }
200 return _runPlan(channel, {Plan::interferenceMonitor()});
201}
202
203bool Protocol::_configureLegacyDevice(GPSCommandChannel& channel)
204{
205 const auto rate = Wire::encode(Plan::LEGACY_SURVEY_RATE);
206 const auto navigation = Wire::encode(Plan::LEGACY_NAVIGATION);
207 if (!_sendAcknowledged(channel, Msg::CFG_RATE, rate)) {
208 return false;
209 }
210 if (!_sendAcknowledged(channel, Msg::CFG_NAV5, navigation)) {
211 return false;
212 }
213 for (const auto& output : Plan::legacyMessageOutput()) {
214 if (!_setMessageRateAcknowledged(channel, output)) {
215 return false;
216 }
217 }
218 return true;
219}
220
221bool Protocol::_restartSurveyIn(GPSCommandChannel& channel)
222{
223 if (!_identity.protocol27) {
224 return _restartLegacySurveyIn(channel);
225 }
226 (void) _transactValset(channel, Plan::disableRTCMOutput());
227 if (const auto* fixed = std::get_if<GPSBaseStationConfig::Fixed>(&_base.mode)) {
228 if (!_transactValset(channel, Plan::fixedBase(*fixed)).succeeded()) {
229 return false;
230 }
231 return _activateRTCMOutput(channel) == RTCMActivation::Active;
232 }
233 const auto* survey = std::get_if<GPSBaseStationConfig::SurveyIn>(&_base.mode);
234 if (!survey) {
235 return false;
236 }
237 // Reapplying survey-in mode does not restart an existing survey.
238 if (!_disableTimeMode(channel)) {
239 return false;
240 }
241 if (!_waitForSurveyStop(channel)) {
242 return false;
243 }
244 return _transactValset(channel, Plan::surveyIn(*survey)).succeeded();
245}
246
247bool Protocol::_restartLegacySurveyIn(GPSCommandChannel& channel)
248{
249 for (const auto& output : Plan::legacyDisableRTCMOutput()) {
250 (void) _setMessageRate(channel, output);
251 }
252 const auto* fixed = std::get_if<GPSBaseStationConfig::Fixed>(&_base.mode);
253 if (!_disableTimeMode(channel)) {
254 return false;
255 }
256 if (fixed) {
257 const auto timeMode = Wire::encode(Plan::legacyFixedBase(*fixed));
258 if (!_sendAcknowledged(channel, Msg::CFG_TMODE3, timeMode)) {
259 return false;
260 }
261 return _activateRTCMOutput(channel) == RTCMActivation::Active;
262 }
263 const auto* survey = std::get_if<GPSBaseStationConfig::SurveyIn>(&_base.mode);
264 if (!survey) {
265 return false;
266 }
267 if (!_waitForSurveyStop(channel)) {
268 return false;
269 }
270 const auto timeMode = Wire::encode(Plan::legacySurveyIn(*survey));
271 if (!_sendAcknowledged(channel, Msg::CFG_TMODE3, timeMode)) {
272 return false;
273 }
274 return _setMessageRateAcknowledged(channel, Plan::LEGACY_SURVEY_STATUS);
275}
276
277bool Protocol::_disableTimeMode(GPSCommandChannel& channel)
278{
279 if (_identity.protocol27) {
280 if (!_transactValset(channel, Plan::disableTimeMode()).succeeded()) {
281 return false;
282 }
283 return _verifyValset(channel,
284 {"UBX-CFG-VALGET " + QByteArray::number(Cfg::TMODE_MODE.id), Plan::CONFIG_TIMEOUT})
285 .succeeded();
286 }
287 const auto disabled = Wire::encode(UBX::CfgTmode3{});
288 if (!_sendAcknowledged(channel, Msg::CFG_TMODE3, disabled)) {
289 return false;
290 }
291 _timeModeReadbackPending = true;
292 _timeModeReadback.reset();
293 if (!_send(channel, Msg::CFG_TMODE3, {}, {"UBX-CFG-TMODE3 disabled readback", Plan::CONFIG_TIMEOUT})) {
294 _timeModeReadbackPending = false;
295 return false;
296 }
297 const auto result = channel.awaitReply([this] {
298 if (_controller.lateRejection()) {
299 return GPSCommandOutcome::Rejected;
300 }
301 return !_timeModeReadback ? GPSCommandOutcome::Pending
302 : *_timeModeReadback == 0 ? GPSCommandOutcome::ReadbackVerified
304 });
305 _timeModeReadbackPending = false;
307}
308
309bool Protocol::_waitForSurveyStop(GPSCommandChannel& channel)
310{
311 _surveyStopped = false;
312 const uint64_t deadline = GPSDeadline::after(channel.nowUs(), Plan::SURVEY_STOP_TIMEOUT).untilUs;
313 while (!_surveyStopped && channel.nowUs() < deadline) {
314 if (!_send(channel, Msg::NAV_SVIN, {}, {"UBX-NAV-SVIN stopped", Plan::CONFIG_TIMEOUT})) {
315 return false;
316 }
317 (void) channel.receiveUntil([this] { return _surveyStopped; }, Plan::SURVEY_STOP_POLL);
318 if (channel.failed()) {
319 return false;
320 }
321 }
322 if (!_surveyStopped) {
323 qCWarning(UBXProtocolLog) << "Time mode did not stop";
325 return false;
326 }
328 return true;
329}
330
331Protocol::RTCMActivation Protocol::_activateRTCMOutput(GPSCommandChannel& channel)
332{
333 if (!_identity.protocol27) {
334 return _activateLegacyRTCMOutput(channel);
335 }
336 const bool activated = _transactValset(channel, Plan::rtcmOutput(_base.compactObservations)).succeeded();
337 return activated ? RTCMActivation::Active : RTCMActivation::Failed;
338}
339
340Protocol::RTCMActivation Protocol::_activateLegacyRTCMOutput(GPSCommandChannel& channel)
341{
342 auto& progress = _legacyRTCM;
343 const auto rate = Wire::encode(Plan::LEGACY_BASE_RATE);
344 if (!_send(channel, Msg::CFG_RATE, rate, {{}, Plan::LEGACY_REPLY_TIMEOUT})) {
345 progress = {};
346 return RTCMActivation::Failed;
347 }
348 // A CFG-MSG acknowledgement does not name the message, so replies are attributed by their order: the base-rate
349 // reply names CFG-RATE and follows any late reply to an earlier activation, and each later command waits for its
350 // own. A rate left unanswered is polled; once a poll expires with a reply still to come, later replies cannot be
351 // attributed, so no more rates are written and activation depends on the outputs confirmed so far. An unanswered
352 // base rate leaves activation to a later service; a rejected one leaves RTCM at the survey rate.
353 const auto baseRate = _waitForAck(channel, Msg::CFG_RATE).outcome;
354 if (baseRate == GPSCommandOutcome::TimedOut) {
355 return RTCMActivation::Unanswered;
356 }
357 if (baseRate != GPSCommandOutcome::Acknowledged && baseRate != GPSCommandOutcome::Rejected) {
358 progress = {};
359 return RTCMActivation::Failed;
360 }
361 // The base status rates come first; their rejection is ignored, as that of an optional RTCM message.
362 const auto status = Plan::legacyBaseStatus();
363 const auto rtcm = Plan::legacyRTCMOutput(_base.compactObservations);
364 bool unanswered = false;
365 for (; progress.next < status.size() + rtcm.size(); ++progress.next) {
366 // Send a rate only with time for its poll; a late reply would be credited to the next command.
367 if (channel.operationDeadline().remaining(channel.nowUs()) <
368 Plan::CONFIG_TIMEOUT + Plan::LEGACY_REPLY_TIMEOUT) {
369 qCDebug(UBXProtocolLog) << "Service budget spent; RTCM activation resumes in the next service";
370 return RTCMActivation::Unanswered;
371 }
372 const auto output = progress.next < status.size()
373 ? Plan::RTCMOutput{status[progress.next], Plan::RTCMContent::Optional}
374 : rtcm[progress.next - status.size()];
375 const auto reply = _confirmMessageRate(channel, output.rate);
376 if (reply.outcome == GPSCommandOutcome::TimedOut) {
377 unanswered = true;
378 break;
379 }
380 if (reply.outcome != GPSCommandOutcome::Acknowledged && reply.outcome != GPSCommandOutcome::ReadbackVerified &&
381 reply.outcome != GPSCommandOutcome::Rejected) {
382 progress = {};
383 return RTCMActivation::Failed;
384 }
385 if (reply.outcome != GPSCommandOutcome::Rejected) {
386 progress.stationPosition = progress.stationPosition || output.content == Plan::RTCMContent::StationPosition;
387 progress.observations = progress.observations || output.content == Plan::RTCMContent::Observations;
388 }
389 if (!reply.settled) {
390 ++progress.next;
391 unanswered = true;
392 break;
393 }
394 }
395 const bool active = progress.stationPosition && progress.observations;
396 if (unanswered) {
397 qCWarning(UBXProtocolLog) << "Receiver did not answer a CFG-MSG rate or its poll; later RTCM messages are not"
398 " configured";
399 if (!active) {
400 return RTCMActivation::Unanswered;
401 }
402 } else if (!active) {
403 qCWarning(UBXProtocolLog) << "Receiver rejected the RTCM station position or every observation message";
404 }
405 progress = {};
406 return active ? RTCMActivation::Active : RTCMActivation::Failed;
407}
408
409void Protocol::serviceStreaming(GPSCommandChannel& channel)
410{
411 auto& requests = _requests;
412 if (requests.rtcmActivation) {
413 const auto activation = _activateRTCMOutput(channel);
414 // A slow receiver keeps its session and finished survey: the next service resumes activation. Survey reports
415 // decoded meanwhile do not request another.
416 requests.rtcmActivation = activation == RTCMActivation::Unanswered;
417 if (activation == RTCMActivation::Failed) {
418 channel.failControl(QStringLiteral("u-blox receiver did not start RTCM output after the survey-in"));
419 }
420 }
421 if (const uint16_t message = std::exchange(requests.disableMessage, 0)) {
422 _disableUnexpectedMessage(channel, message);
423 }
424}
425
426void Protocol::_disableUnexpectedMessage(GPSCommandChannel& channel, uint16_t message)
427{
428 const uint64_t now = channel.nowUs();
429 if (now <= _lastDisableUs + static_cast<uint64_t>(Plan::DISABLE_MESSAGE_INTERVAL.count())) {
430 return;
431 }
432 if (!_identity.protocol27) {
433 _lastDisableUs = now;
434 (void) _setMessageRate(channel,
435 {{static_cast<uint8_t>(message), static_cast<uint8_t>(message >> 8)}, Plan::NO_OUTPUT});
436 return;
437 }
438 const auto output = std::ranges::find(Plan::UNEXPECTED_OUTPUT, message,
439 [](const Plan::UnexpectedOutput& entry) { return entry.message.value(); });
440 if (output != Plan::UNEXPECTED_OUTPUT.end() && _ready) {
441 _lastDisableUs = now;
442 (void) _writeValset(channel, Plan::disableMessage(output->key));
443 }
444}
445
446bool Protocol::_send(GPSCommandChannel& channel, UBX::MessageId message, std::span<const uint8_t> payload,
448{
449 if (step.command.isEmpty()) {
450 step.command = UBX::messageName(message);
451 }
452 channel.beginCommand(std::move(step));
453 // Identity replies can take two seconds, but writing the frame keeps the shorter configuration cap.
454 const auto scope = channel.deadlineScope(Plan::CONFIG_TIMEOUT);
455 std::array<uint8_t, UBX::FRAME_OVERHEAD + Plan::VALSET_CAPACITY> buffer{};
456 const auto frame = UBX::encodeFrame(message, payload, buffer);
457 if (frame.empty()) {
459 return false;
460 }
461 return channel.write(frame);
462}
463
464bool Protocol::_sendAcknowledged(GPSCommandChannel& channel, UBX::MessageId message, std::span<const uint8_t> payload)
465{
466 if (!_send(channel, message, payload, {{}, Plan::CONFIG_TIMEOUT})) {
467 return false;
468 }
469 return _waitForAck(channel, message).succeeded();
470}
471
472GPSConfigurationEvidence Protocol::_waitForAck(GPSCommandChannel& channel, UBX::MessageId message)
473{
474 auto& controller = _controller;
475 const bool valset = message.value() == Msg::CFG_VALSET.value();
476 controller.beginAcknowledgement(message.value());
477 if (valset && controller.configurationReadbackRequired()) {
478 controller.finishAcknowledgement();
479 const GPSDeadline commandDeadline = channel.commandDeadline();
480 const auto scope = channel.deadlineScope(commandDeadline);
481 const auto& command = channel.currentCommand();
482 return _verifyValset(channel,
483 {"UBX-CFG-VALSET readback", commandDeadline.remaining(channel.nowUs()), command.required});
484 }
485 const auto result = channel.awaitReply([&controller] { return controller.acknowledgement(); });
486 controller.finishAcknowledgement();
487 if (valset && result.outcome == GPSCommandOutcome::TimedOut) {
488 _valsetAckAmbiguous = true;
489 }
490 return result;
491}
492
493bool Protocol::_setMessageRate(GPSCommandChannel& channel, UBX::Plan::MessageRate rate)
494{
495 const auto payload = Wire::encode(UBX::CfgMsg{.msg = rate.message.value(), .rate = rate.rate});
496 return _send(channel, Msg::CFG_MSG, payload, {{}, Plan::CONFIG_TIMEOUT});
497}
498
499bool Protocol::_setMessageRateAcknowledged(GPSCommandChannel& channel, UBX::Plan::MessageRate rate)
500{
501 return _setMessageRateReply(channel, rate) == GPSCommandOutcome::Acknowledged;
502}
503
504GPSCommandOutcome Protocol::_setMessageRateReply(GPSCommandChannel& channel, UBX::Plan::MessageRate rate)
505{
506 if (!_setMessageRate(channel, rate)) {
508 }
509 return _waitForAck(channel, Msg::CFG_MSG).outcome;
510}
511
512Protocol::RateConfirmation Protocol::_confirmMessageRate(GPSCommandChannel& channel, UBX::Plan::MessageRate rate)
513{
514 const auto outcome = _setMessageRateReply(channel, rate);
515 if (outcome != GPSCommandOutcome::TimedOut) {
516 return RateConfirmation{outcome, true};
517 }
518 auto& controller = _controller;
519 const std::array<uint8_t, 2> poll{rate.message.cls, rate.message.id};
520 controller.beginRatePoll(rate.message.value());
521 if (!_send(channel, Msg::CFG_MSG, poll,
522 {"UBX-CFG-MSG " + UBX::messageName(rate.message) + " readback", Plan::LEGACY_REPLY_TIMEOUT})) {
523 controller.finishRatePoll();
524 return RateConfirmation{GPSCommandOutcome::TransportError, false};
525 }
526 // The 3-byte CFG-MSG sets the rate of the port it arrives on, which is UART1 or USB.
527 const auto decide = [&controller, expected = rate.rate] {
528 if (const auto rates = controller.polledRates()) {
529 return (*rates)[Plan::LEGACY_PORT_UART1] == expected || (*rates)[Plan::LEGACY_PORT_USB] == expected
532 }
533 return controller.ratePollRejected() ? GPSCommandOutcome::Rejected : GPSCommandOutcome::TimedOut;
534 };
535 const auto result = channel.awaitReply([&channel, &controller, decide] {
536 const bool expired = channel.nowUs() >= channel.commandDeadline().untilUs;
537 return controller.ratePollSettled() || expired ? decide() : GPSCommandOutcome::Pending;
538 });
539 const bool settled = controller.ratePollSettled();
540 controller.finishRatePoll();
541 return RateConfirmation{result.outcome, settled};
542}
543
544bool Protocol::_runPlan(GPSCommandChannel& channel, const std::vector<UBX::Plan::ValsetBatch>& batches)
545{
546 for (const auto& batch : batches) {
547 if (!batch.included) {
548 continue;
549 }
550 const bool acknowledged = _transactValset(channel, batch).succeeded();
551 if (channel.failed() || (!acknowledged && batch.policy == NakPolicy::Fail)) {
552 return false;
553 }
554 if (!acknowledged && batch.policy == NakPolicy::Warn) {
555 qCWarning(UBXProtocolLog) << batch.warning;
556 }
557 }
558 return true;
559}
560
561bool Protocol::_valsetsRefused() const
562{
563 return _valsetAckAmbiguous && !_controller.configurationReadbackRequired();
564}
565
566void Protocol::_load(const UBX::Plan::ValsetBatch& batch)
567{
568 _valset = {};
569 for (const auto& item : batch.items) {
570 if (!item.included) {
571 continue;
572 }
573 if (item.msgOut) {
574 const UBX::MsgOutKey family{{item.key}};
575 (void) _valset.append(family.port(UBX::MsgOutPort::UART1).id, item.value);
576 (void) _valset.append(family.port(UBX::MsgOutPort::USB).id, item.value);
577 } else {
578 (void) _valset.append(item.key, item.value);
579 }
580 }
581}
582
583bool Protocol::_writeValset(GPSCommandChannel& channel, const UBX::Plan::ValsetBatch& batch)
584{
585 _load(batch);
586 const auto payload = _valset.payload();
587 GPSConfigurationStep step{{}, batch.timeout, batch.required()};
588 if (payload.empty() || _valsetsRefused()) {
590 channel.beginCommand(std::move(step));
592 return false;
593 }
594 return _send(channel, Msg::CFG_VALSET, payload, std::move(step));
595}
596
597GPSConfigurationEvidence Protocol::_transactValset(GPSCommandChannel& channel, const UBX::Plan::ValsetBatch& batch)
598{
599 if (!_writeValset(channel, batch)) {
600 return channel.completeCommand(channel.error() == GPSProtocolError::Cancelled
603 }
604 return _awaitValsetAck(channel);
605}
606
607GPSConfigurationEvidence Protocol::_awaitValsetAck(GPSCommandChannel& channel)
608{
609 auto result = _waitForAck(channel, Msg::CFG_VALSET);
610 if (result.outcome != GPSCommandOutcome::TimedOut || result.required ||
611 _controller.configurationReadbackRequired()) {
612 return result;
613 }
614 // A VALSET acknowledgement does not say which VALSET it answers, so a late one would be taken for the next. The
615 // receiver answers in order: the readback reply follows any late reply to this batch, so a late ACK and matching
616 // values accept it, a late NAK, other values or a NAK of the readback reject it, and either way nothing of it is
617 // left to arrive. Required batches still fail on the timeout.
618 result = _verifyValset(channel, {"UBX-CFG-VALSET readback", Plan::CONFIG_TIMEOUT, false});
619 if (result.outcome == GPSCommandOutcome::ReadbackVerified || result.outcome == GPSCommandOutcome::Rejected) {
620 _valsetAckAmbiguous = false;
621 }
622 return result;
623}
624
625GPSConfigurationEvidence Protocol::_verifyValset(GPSCommandChannel& channel, GPSConfigurationStep step)
626{
627 auto& controller = _controller;
628 const auto scope = channel.deadlineScope(step.timeout);
629 UBX::ConfigurationValueCursor cursor(_valset.entries());
631 while (!cursor.empty()) {
633 std::array<uint32_t, UBX::ConfigurationValues::MAX_KEYS> keys{};
634 std::array<uint8_t, 4 + UBX::ConfigurationValues::MAX_KEYS * sizeof(uint32_t)> request{};
635 while (!cursor.empty() && expected.count < expected.values.size()) {
636 const auto entry = cursor.next();
637 if (!entry) {
638 channel.beginCommand(step);
640 }
641 (void) LittleEndian::write(request, 4 + expected.count * sizeof(uint32_t), entry->key);
642 keys[expected.count] = entry->key;
643 expected.values[expected.count++] = *entry;
644 }
645 controller.beginReadback(std::span(keys).first(expected.count));
646 if (!_send(channel, Msg::CFG_VALGET, std::span(request).first(4 + expected.count * sizeof(uint32_t)), step)) {
647 controller.finishReadback();
649 }
650 result = channel.awaitReply([&controller, &expected] {
651 if (controller.lateRejection() || controller.readbackRejected()) {
653 }
654 if (!controller.readbackReady()) {
656 }
657 return controller.readback().values == expected.values ? GPSCommandOutcome::ReadbackVerified
659 });
660 controller.finishReadback();
662 return result;
663 }
664 }
665 return result;
666}
667
668namespace {
669
670constexpr auto MON_VER_POLL = [] {
671 std::array<uint8_t, FRAME_OVERHEAD> frame{};
672 (void) encodeFrame(Msg::MON_VER, {}, frame);
673 return frame;
674}();
675
676QLatin1StringView signature(const GPSFrame& frame)
677{
678 if (frame.kind == GPSFrameKind::UBX) {
679 return QLatin1StringView("UBX frames");
680 }
681 const std::string_view text = frame.text();
682 if (frame.kind != GPSFrameKind::ASCIILine || !text.starts_with("$PUBX,")) {
683 return {};
684 }
685 const auto sentence = NMEA::frame(text);
686 return sentence && sentence->hasValidChecksum() ? QLatin1StringView("$PUBX sentences") : QLatin1StringView();
687}
688
689} // namespace
690
692bool Protocol::probe(GPSCommandChannel& channel)
693{
694 const auto result =
695 channel.transact({UBX::messageName(Msg::MON_VER), Plan::IDENTITY_TIMEOUT}, MON_VER_POLL,
696 GPSFrameMatcher([](const GPSFrame& frame) {
697 return frame.kind == GPSFrameKind::UBX && frame.messageId == Msg::MON_VER.value()
700 }));
701 return result.succeeded();
702}
703
704const GPSReceiverFamily FAMILY{
705 .type = GPSType::ublox,
706 .logCategory = &UBXProtocolLog,
707 .stream = {.framers = GPSFrameKind::UBX | GPSFrameKind::RTCM3, .enabled = GPSFrameKind::UBX},
708 .baudCandidates = Plan::BAUD_RATES,
709 .create = &gpsCreateProtocol<Protocol>,
710 .signature = &signature,
711};
712
713} // namespace UBX
GPSBaudProbe
@ Stop
The receiver answered but is not usable, so no other rate is tried.
std::function< GPSCommandOutcome(const GPSFrame &frame)> GPSFrameMatcher
Resolves the outstanding command from each dispatched frame, after the family decoded it; Pending kee...
Definition GPSCommand.h:71
GPSCommandOutcome
Definition GPSCommand.h:33
@ Cancelled
A stop was requested; not a receiver or link fault.
@ 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.
Config config
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::array< MsgOutKey, 4 > keys
Definition UBXPlan.cc:13
void beginCommand(GPSConfigurationStep step)
GPSBaudDetection detectBaud(std::span< const unsigned > candidates, unsigned baud, GPSBaudProbeFunction probe)
uint64_t nowUs() const
GPSReceiveUpdates receiveCycle(std::chrono::milliseconds timeout)
GPSDeadline operationDeadline() const
bool setBaudrate(unsigned baudrate)
GPSDeadline commandDeadline() const
Deadline of the current command attempt, set by beginCommand().
void failControl(GPSProtocolError failure=GPSProtocolError::Protocol)
GPSDeadlineScope deadlineScope(std::chrono::milliseconds timeout)
bool receiveUntil(std::function< bool()> done, std::chrono::milliseconds duration)
const GPSConfigurationEvidence & currentCommand() const
GPSConfigurationEvidence completeCommand(GPSCommandOutcome outcome)
Retires the current attempt and reports it; repeated completion returns the retained evidence unchang...
GPSStreamDemux & stream()
bool write(QByteArrayView bytes)
GPSConfigurationEvidence awaitReply(GPSReplyPoll reply)
Decodes received traffic until reply resolves the current command, its deadline expires,...
GPSProtocolError error() const
void failNoAnswer(std::span< const unsigned > rates)
GPSConfigurationEvidence transact(GPSConfigurationStep step, QByteArrayView bytes)
void finishCommand(GPSCommandOutcome outcome)
Reads only key/value entries; VALSET and VALGET validate their different headers separately.
constexpr std::array BAUD_RATES
Rates the port query probes, in order. Trimble receivers default to 115200.
Definition AshtechPlan.h:31
constexpr bool write(std::span< uint8_t > bytes, std::size_t offset, T value)
Encode an unaligned little-endian scalar. Insufficient space leaves the destination unchanged.
std::optional< Sentence > sentence(std::string_view text)
std::optional< Frame > frame(std::string_view text)
GPSCommandSequence::Command command(QByteArrayView body, GPSTextMatcher reply, std::chrono::milliseconds timeout)
body labelled by its text, framed as a sentence, and completed by reply within timeout.
constexpr auto TMODE_MODE
constexpr MessageId CFG_VALSET
Definition UBXFrame.h:66
constexpr MessageId NAV_SVIN
Definition UBXFrame.h:51
constexpr MessageId CFG_VALGET
Definition UBXFrame.h:67
constexpr MessageId CFG_RATE
Definition UBXFrame.h:63
constexpr MessageId MON_VER
Definition UBXFrame.h:68
constexpr MessageId CFG_MSG
Definition UBXFrame.h:62
constexpr MessageId CFG_NAV5
Definition UBXFrame.h:64
constexpr MessageId CFG_TMODE3
Definition UBXFrame.h:65
constexpr MessageId CFG_PRT
Definition UBXFrame.h:61
std::array< MessageRate, 5 > legacyMessageOutput()
NAV-PVT, status, satellite and MON-HW (antenna and jamming) rates during the survey.
Definition UBXPlan.cc:230
std::vector< ValsetBatch > portsAndNavigation(const ReceiverProfile &profile)
Protocol 27+ configuration before jamming detection: ports, navigation, odometer and RTK.
Definition UBXPlan.cc:77
std::array< MessageRate, 10 > legacyDisableRTCMOutput()
Written without waiting for acknowledgements.
Definition UBXPlan.cc:235
std::vector< ValsetBatch > messageOutput(const ReceiverProfile &profile)
Protocol 27+ configuration after jamming detection: message output and receiver-specific extras.
Definition UBXPlan.cc:118
u-blox receivers over UBX: CFG-VALSET configuration from protocol 27, CFG-MSG and CFG-TMODE3 before i...
constexpr ReceiverProfile receiverProfile(Board board)
Definition UBXPlan.h:82
Board
Definition UBXPlan.h:17
constexpr std::span< const uint8_t > encodeFrame(MessageId message, std::span< const uint8_t > payload, std::span< uint8_t > out)
Writes the frame of payload to the front of out.
Definition UBXFrame.h:130
QByteArray messageName(MessageId message)
The name of message, such as "UBX-CFG-VALSET", or its class and id in hex when it has none.
Definition UBXFrame.h:104
constexpr std::array< uint8_t, SIZE< T > > encode(const T &value)
Definition WireFields.h:136
The receiver configuration requested for one configure() attempt.
Evidence from one configuration command, not a claim of physical receiver operation.
Definition GPSCommand.h:46
GPSCommandOutcome outcome
Definition GPSCommand.h:48
std::chrono::milliseconds timeout
Definition GPSCommand.h:64
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
The values of one CFG-VALGET poll or response.
std::array< ConfigurationValue, MAX_KEYS > values
static constexpr size_t MAX_KEYS
One poll reads at most this many keys.
uint8_t cls
Definition UBXFrame.h:24
constexpr uint16_t value() const
Definition UBXFrame.h:27
uint8_t id
Definition UBXFrame.h:25
A CFG-MSGOUT output rate key family, named by its I2C key.
CFG-MSG output rate of one message, as a divisor of the measurement rate.
Definition UBXPlan.h:224
std::chrono::milliseconds timeout
Definition UBXPlan.h:182
bool required() const
Definition UBXPlan.h:186