7#include <QtCore/QByteArrayView>
26bool Protocol::_baseStationUnsupported()
const
28 const auto& identity = _identity;
30 if (identity.timeModeUnsupported) {
34 return !identity.isM8p && !identity.model.isEmpty();
37 return !profile.rtcmOutput && profile.baseCapabilityKnown;
43 _valsetAckAmbiguous =
false;
46 _setMode({}, channel.
stream());
50 const unsigned requestedBaud = baud;
58 const bool identified = _identify(channel);
61 const auto& identity = _identity;
63 if (!detection.found) {
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));
76 if (_baseStationUnsupported()) {
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));
82 const unsigned desiredBaud = requestedBaud ? requestedBaud : Plan::BASE_BAUD;
83 if (!_setUpPort(channel, detection.baud, desiredBaud)) {
87 _setMode({.corrections =
true}, channel.
stream());
89 bool deviceConfigured =
false;
90 if (identity.protocol27) {
91 deviceConfigured = _configureDevice(channel);
93 deviceConfigured = _configureLegacyDevice(channel);
95 if (!deviceConfigured) {
98 if (!_restartSurveyIn(channel)) {
102 _mode.navigation =
true;
108 const auto scope = channel.
deadlineScope(Plan::IDENTITY_TIMEOUT);
110 if (!_send(channel,
Msg::MON_VER, {}, {{}, Plan::IDENTITY_TIMEOUT})) {
116bool Protocol::_setUpPort(
GPSCommandChannel& channel,
unsigned detectedBaud,
unsigned desiredBaud)
118 const bool modern = _identity.protocol27;
120 if (!_transactValset(channel, Plan::uart1Protocols()).succeeded()) {
124 const auto ports = Plan::legacyPorts(detectedBaud);
129 if (desiredBaud == detectedBaud) {
133 const UBX::Board identifiedBoard = _identity.board;
136 if (!_writeValset(channel, Plan::uart1Baudrate(desiredBaud))) {
140 const auto ports = Plan::legacyPorts(desiredBaud);
141 if (!_send(channel,
Msg::CFG_PRT, ports, {{}, Plan::CONFIG_TIMEOUT,
false})) {
145 const auto result = _waitForAck(channel, command);
146 const bool acknowledged = result.succeeded();
151 channel.
failControl(QStringLiteral(
"The link cannot run at %1 baud").arg(desiredBaud));
154 if (modern && !acknowledged) {
156 _controller.requireConfigurationReadback(
command.value());
159 if (!_identify(channel)) {
162 if (_identity.board != identifiedBoard || _identity.protocol27 != modern || _controller.lateRejection()) {
165 if (modern && !acknowledged) {
166 return _verifyValset(channel, {
"UBX-CFG-VALSET readback", Plan::CONFIG_TIMEOUT}).succeeded();
175 if (!_runPlan(channel, portsAndNavigation)) {
178 if (!_configureJammingDetection(channel)) {
183 return _runPlan(channel, messageOutput);
190 const auto probe = _transactValset(channel, Plan::jammingDetection());
191 if (probe.succeeded()) {
196 qCWarning(UBXProtocolLog) <<
"CFG-SEC-JAMDET probe unanswered";
200 return _runPlan(channel, {Plan::interferenceMonitor()});
205 const auto rate =
Wire::encode(Plan::LEGACY_SURVEY_RATE);
206 const auto navigation =
Wire::encode(Plan::LEGACY_NAVIGATION);
210 if (!_sendAcknowledged(channel,
Msg::CFG_NAV5, navigation)) {
214 if (!_setMessageRateAcknowledged(channel, output)) {
223 if (!_identity.protocol27) {
224 return _restartLegacySurveyIn(channel);
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()) {
231 return _activateRTCMOutput(channel) == RTCMActivation::Active;
233 const auto* survey = std::get_if<GPSBaseStationConfig::SurveyIn>(&_base.mode);
238 if (!_disableTimeMode(channel)) {
241 if (!_waitForSurveyStop(channel)) {
244 return _transactValset(channel, Plan::surveyIn(*survey)).succeeded();
250 (void) _setMessageRate(channel, output);
252 const auto* fixed = std::get_if<GPSBaseStationConfig::Fixed>(&_base.mode);
253 if (!_disableTimeMode(channel)) {
257 const auto timeMode =
Wire::encode(Plan::legacyFixedBase(*fixed));
261 return _activateRTCMOutput(channel) == RTCMActivation::Active;
263 const auto* survey = std::get_if<GPSBaseStationConfig::SurveyIn>(&_base.mode);
267 if (!_waitForSurveyStop(channel)) {
270 const auto timeMode =
Wire::encode(Plan::legacySurveyIn(*survey));
274 return _setMessageRateAcknowledged(channel, Plan::LEGACY_SURVEY_STATUS);
279 if (_identity.protocol27) {
280 if (!_transactValset(channel, Plan::disableTimeMode()).succeeded()) {
283 return _verifyValset(channel,
284 {
"UBX-CFG-VALGET " + QByteArray::number(
Cfg::TMODE_MODE.
id), Plan::CONFIG_TIMEOUT})
291 _timeModeReadbackPending =
true;
292 _timeModeReadback.reset();
293 if (!_send(channel,
Msg::CFG_TMODE3, {}, {
"UBX-CFG-TMODE3 disabled readback", Plan::CONFIG_TIMEOUT})) {
294 _timeModeReadbackPending =
false;
297 const auto result = channel.
awaitReply([
this] {
298 if (_controller.lateRejection()) {
299 return GPSCommandOutcome::Rejected;
305 _timeModeReadbackPending =
false;
311 _surveyStopped =
false;
313 while (!_surveyStopped && channel.
nowUs() < deadline) {
314 if (!_send(channel,
Msg::NAV_SVIN, {}, {
"UBX-NAV-SVIN stopped", Plan::CONFIG_TIMEOUT})) {
317 (void) channel.
receiveUntil([
this] {
return _surveyStopped; }, Plan::SURVEY_STOP_POLL);
322 if (!_surveyStopped) {
323 qCWarning(UBXProtocolLog) <<
"Time mode did not stop";
331Protocol::RTCMActivation Protocol::_activateRTCMOutput(
GPSCommandChannel& channel)
333 if (!_identity.protocol27) {
334 return _activateLegacyRTCMOutput(channel);
336 const bool activated = _transactValset(channel, Plan::rtcmOutput(_base.compactObservations)).succeeded();
337 return activated ? RTCMActivation::Active : RTCMActivation::Failed;
340Protocol::RTCMActivation Protocol::_activateLegacyRTCMOutput(
GPSCommandChannel& channel)
342 auto& progress = _legacyRTCM;
344 if (!_send(channel,
Msg::CFG_RATE, rate, {{}, Plan::LEGACY_REPLY_TIMEOUT})) {
346 return RTCMActivation::Failed;
353 const auto baseRate = _waitForAck(channel,
Msg::CFG_RATE).outcome;
355 return RTCMActivation::Unanswered;
359 return RTCMActivation::Failed;
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) {
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;
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);
383 return RTCMActivation::Failed;
386 progress.stationPosition = progress.stationPosition || output.content == Plan::RTCMContent::StationPosition;
387 progress.observations = progress.observations || output.content == Plan::RTCMContent::Observations;
389 if (!reply.settled) {
395 const bool active = progress.stationPosition && progress.observations;
397 qCWarning(UBXProtocolLog) <<
"Receiver did not answer a CFG-MSG rate or its poll; later RTCM messages are not"
400 return RTCMActivation::Unanswered;
402 }
else if (!active) {
403 qCWarning(UBXProtocolLog) <<
"Receiver rejected the RTCM station position or every observation message";
406 return active ? RTCMActivation::Active : RTCMActivation::Failed;
411 auto& requests = _requests;
412 if (requests.rtcmActivation) {
413 const auto activation = _activateRTCMOutput(channel);
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"));
421 if (
const uint16_t message = std::exchange(requests.disableMessage, 0)) {
422 _disableUnexpectedMessage(channel, message);
426void Protocol::_disableUnexpectedMessage(
GPSCommandChannel& channel, uint16_t message)
428 const uint64_t now = channel.
nowUs();
429 if (now <= _lastDisableUs +
static_cast<uint64_t
>(Plan::DISABLE_MESSAGE_INTERVAL.count())) {
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});
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));
454 const auto scope = channel.
deadlineScope(Plan::CONFIG_TIMEOUT);
455 std::array<uint8_t, UBX::FRAME_OVERHEAD + Plan::VALSET_CAPACITY> buffer{};
461 return channel.
write(frame);
466 if (!_send(channel, message, payload, {{}, Plan::CONFIG_TIMEOUT})) {
469 return _waitForAck(channel, message).succeeded();
474 auto& controller = _controller;
476 controller.beginAcknowledgement(message.
value());
477 if (valset && controller.configurationReadbackRequired()) {
478 controller.finishAcknowledgement();
482 return _verifyValset(channel,
485 const auto result = channel.
awaitReply([&controller] {
return controller.acknowledgement(); });
486 controller.finishAcknowledgement();
488 _valsetAckAmbiguous =
true;
496 return _send(channel,
Msg::CFG_MSG, payload, {{}, Plan::CONFIG_TIMEOUT});
506 if (!_setMessageRate(channel, rate)) {
514 const auto outcome = _setMessageRateReply(channel, rate);
516 return RateConfirmation{outcome,
true};
518 auto& controller = _controller;
523 controller.finishRatePoll();
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
535 const auto result = channel.
awaitReply([&channel, &controller, decide] {
539 const bool settled = controller.ratePollSettled();
540 controller.finishRatePoll();
541 return RateConfirmation{result.
outcome, settled};
544bool Protocol::_runPlan(
GPSCommandChannel& channel,
const std::vector<UBX::Plan::ValsetBatch>& batches)
546 for (
const auto& batch : batches) {
547 if (!batch.included) {
550 const bool acknowledged = _transactValset(channel, batch).succeeded();
551 if (channel.
failed() || (!acknowledged && batch.policy == NakPolicy::Fail)) {
554 if (!acknowledged && batch.policy == NakPolicy::Warn) {
555 qCWarning(UBXProtocolLog) << batch.warning;
561bool Protocol::_valsetsRefused()
const
563 return _valsetAckAmbiguous && !_controller.configurationReadbackRequired();
569 for (
const auto& item : batch.items) {
570 if (!item.included) {
578 (void) _valset.append(item.key, item.value);
586 const auto payload = _valset.payload();
588 if (payload.empty() || _valsetsRefused()) {
599 if (!_writeValset(channel, batch)) {
604 return _awaitValsetAck(channel);
611 _controller.configurationReadbackRequired()) {
618 result = _verifyValset(channel, {
"UBX-CFG-VALSET readback", Plan::CONFIG_TIMEOUT,
false});
620 _valsetAckAmbiguous =
false;
627 auto& controller = _controller;
631 while (!cursor.empty()) {
633 std::array<uint32_t, UBX::ConfigurationValues::MAX_KEYS>
keys{};
635 while (!cursor.empty() && expected.
count < expected.
values.size()) {
636 const auto entry = cursor.next();
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();
650 result = channel.
awaitReply([&controller, &expected] {
651 if (controller.lateRejection() || controller.readbackRejected()) {
654 if (!controller.readbackReady()) {
660 controller.finishReadback();
670constexpr auto MON_VER_POLL = [] {
671 std::array<uint8_t, FRAME_OVERHEAD>
frame{};
676QLatin1StringView signature(
const GPSFrame& frame)
679 return QLatin1StringView(
"UBX frames");
681 const std::string_view text =
frame.text();
686 return sentence &&
sentence->hasValidChecksum() ? QLatin1StringView(
"$PUBX sentences") : QLatin1StringView();
705 .
type = GPSType::ublox,
706 .logCategory = &UBXProtocolLog,
709 .create = &gpsCreateProtocol<Protocol>,
710 .signature = &signature,
@ 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...
@ 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.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::array< MsgOutKey, 4 > keys
void beginCommand(GPSConfigurationStep step)
GPSBaudDetection detectBaud(std::span< const unsigned > candidates, unsigned baud, GPSBaudProbeFunction probe)
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.
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
constexpr MessageId NAV_SVIN
constexpr MessageId CFG_VALGET
constexpr MessageId CFG_RATE
constexpr MessageId MON_VER
constexpr MessageId CFG_MSG
constexpr MessageId CFG_NAV5
constexpr MessageId CFG_TMODE3
constexpr MessageId CFG_PRT
std::array< MessageRate, 5 > legacyMessageOutput()
NAV-PVT, status, satellite and MON-HW (antenna and jamming) rates during the survey.
std::vector< ValsetBatch > portsAndNavigation(const ReceiverProfile &profile)
Protocol 27+ configuration before jamming detection: ports, navigation, odometer and RTK.
std::array< MessageRate, 10 > legacyDisableRTCMOutput()
Written without waiting for acknowledgements.
std::vector< ValsetBatch > messageOutput(const ReceiverProfile &profile)
Protocol 27+ configuration after jamming detection: message output and receiver-specific extras.
u-blox receivers over UBX: CFG-VALSET configuration from protocol 27, CFG-MSG and CFG-TMODE3 before i...
constexpr ReceiverProfile receiverProfile(Board board)
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.
QByteArray messageName(MessageId message)
The name of message, such as "UBX-CFG-VALSET", or its class and id in hex when it has none.
constexpr std::array< uint8_t, SIZE< T > > encode(const T &value)
The receiver configuration requested for one configure() attempt.
Evidence from one configuration command, not a claim of physical receiver operation.
GPSCommandOutcome outcome
std::chrono::milliseconds timeout
static GPSDeadline after(uint64_t nowUs, std::chrono::microseconds timeout)
A negative timeout is already due at nowUs.
std::chrono::milliseconds remaining(uint64_t nowUs) const
Rounded up to whole milliseconds and capped at INT32_MAX milliseconds.
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.
constexpr uint16_t value() const
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.
std::chrono::milliseconds timeout