5#include <QtCore/QByteArrayView>
6#include <QtCore/QString>
23constexpr std::chrono::milliseconds PACKET_TIMEOUT{8};
25constexpr std::chrono::milliseconds EPOCH_READ_SLICE{200};
27constexpr uint8_t PVT_VALID_DATE = 0x01;
28constexpr uint8_t PVT_VALID_TIME = 0x02;
29constexpr uint8_t PVT_FULLY_RESOLVED = 0x04;
30constexpr uint8_t FIX_OK = 0x01;
31constexpr uint8_t DIFFERENTIAL_SOLUTION = 0x02;
36 if (!(flags & FIX_OK)) {
46 return Fix::Extrapolated;
56 const uint8_t carrier = flags >> 6;
63 return flags & DIFFERENTIAL_SOLUTION ? Fix::Differential : Fix::Fix3D;
68 return fix != GPSPositionReport::FixType::Unknown && fix != GPSPositionReport::FixType::NoFix;
74QByteArrayView cString(
const uint8_t (&field)[N])
76 const QByteArrayView bytes(field, N - 1);
77 const auto end = bytes.indexOf(
'\0');
78 return end < 0 ? bytes : bytes.first(end);
97template <
typename Header,
typename Block,
typename Count,
typename Assign>
98void decodeSatelliteBlocks(std::span<const uint8_t> payload,
GPSDecodedSatellites& report, Count count, Assign assign)
100 const auto satelliteCount =
102 if (satelliteCount == 0) {
105 for (
size_t index = 0; index < satelliteCount; ++index) {
106 assign(report, Wire::decode<Block>(payload, UBX::WIRE_SIZE<Header> + index * UBX::WIRE_SIZE<Block>));
120 return _decode(
static_cast<uint16_t
>(frame.messageId), frame.payload, context);
125 if (_mode.navigation) {
133 _setMode({.navigation =
true, .corrections =
true}, context.
stream());
140 _setMode({.navigation =
true, .corrections = decode.corrections}, context.
stream());
153 const bool ready = (_timeModeReadbackPending && _timeModeReadback.has_value()) ||
157 _epochStarted =
false;
165 return PACKET_TIMEOUT;
167 return _mode.navigation ? (std::min) (timeout, EPOCH_READ_SLICE) : timeout;
170GPSReceiveUpdates Protocol::_decode(uint16_t message, std::span<const uint8_t> payload,
GPSDecodeContext& context)
176 if (message ==
Msg::NAV_EOE.value() && _mode.navigation) {
177 const uint32_t tow = LittleEndian::read<uint32_t>(payload, 0).value_or(
GPSTime::WEEK_MS);
179 _epochs.
end(tow, context);
183 if (!_accept(message, payload)) {
187 if (schema && schema->towOffset >= 0) {
188 const auto tow = LittleEndian::read<uint32_t>(payload,
static_cast<size_t>(schema->towOffset)).value_or(0);
189 if (
auto* epoch = _epochs.
find(tow, context)) {
190 _decodeEpoch(message, payload, *epoch);
194 const auto updates = _decodeHandled(message, payload, context);
201bool Protocol::_accept(uint16_t message, std::span<const uint8_t> payload)
203 const bool navigation = _mode.navigation;
204 const auto disable = [
this, message] {
205 _requests.disableMessage = message;
213 return _timeModeReadbackPending;
236void Protocol::_decodeEpoch(uint16_t message, std::span<const uint8_t> payload, EpochAssembler::Epoch& epoch)
238 auto& navigation = epoch.position.navigation;
241 const auto pvt = Wire::decode<UBX::NavPvt>(payload);
242 navigation.fixType = navigationFix(pvt.fixType, pvt.flags);
243 epoch.position.velocityValid = velocityValid(navigation.fixType);
246 if (!epoch.highPrecision) {
249 navigation.altitudeMslMeters = pvt.hMSL * 1e-3;
250 navigation.altitudeEllipsoidMeters = pvt.height * 1e-3;
251 navigation.horizontalAccuracyMeters =
static_cast<float>(pvt.hAcc) * 1e-3f;
252 navigation.verticalAccuracyMeters =
static_cast<float>(pvt.vAcc) * 1e-3f;
254 navigation.speedMetersPerSecond =
static_cast<float>(pvt.gSpeed) * 1e-3f;
258 const bool timeValid =
259 (pvt.valid & PVT_VALID_DATE) && (pvt.valid & PVT_VALID_TIME) && (pvt.valid & PVT_FULLY_RESOLVED);
261 pvt.min, pvt.sec, pvt.nano)
264 _epochStarted =
true;
268 const auto hp = Wire::decode<UBX::NavHpposllh>(payload);
274 navigation.altitudeMslMeters = hp.hMSL * 1e-3 + hp.hMSLHp * 1e-4;
275 navigation.altitudeEllipsoidMeters = hp.height * 1e-3 + hp.heightHp * 1e-4;
276 navigation.horizontalAccuracyMeters =
static_cast<float>(hp.hAcc) * 1e-4f;
277 navigation.verticalAccuracyMeters =
static_cast<float>(hp.vAcc) * 1e-4f;
278 epoch.highPrecision =
true;
279 _epochStarted =
true;
283 const auto dop = Wire::decode<UBX::NavDop>(payload);
293GPSReceiveUpdates Protocol::_decodeHandled(uint16_t message, std::span<const uint8_t> payload,
296 const uint64_t now = context.
nowUs();
300 _decodeSatellites(message, payload);
301 for (uint8_t index = 0; index < _satellites.
count; ++index) {
303 system.inViewTimestampUs = now;
305 system.inUseTimestampUs = now;
310 return _decodeSurveyIn(payload, context);
312 const auto status = Wire::decode<UBX::NavStatus>(payload);
318 const auto hardware = Wire::decode<UBX::MonHw>(payload);
331 for (
size_t offset = UBX::WIRE_SIZE<UBX::MonRf> - UBX::WIRE_SIZE<UBX::MonRfBlock>; offset < payload.size();
332 offset += UBX::WIRE_SIZE<UBX::MonRfBlock>) {
333 const auto block = Wire::decode<UBX::MonRfBlock>(payload, offset);
335 antenna = (std::max) (antenna, antennaState(block.antStatus));
344 const auto signal = Wire::decode<UBX::SecSig>(payload);
347 const uint8_t flags = signal.version == 1 ? signal.jamFlags : signal.flags;
356 _decodeMonVer(payload);
362 const QByteArrayView text(payload);
364 const bool outputOverflow = _mode.navigation && text.startsWith(
"txbuf");
365 const auto end = text.indexOf(
'\0');
366 const QString warning =
367 QStringLiteral(
"ubx msg: %1").arg(QString::fromUtf8(end < 0 ? text : text.first(end)));
370 qCDebug(UBXProtocolLog).noquote() << warning;
372 _lastWarning = warning;
373 _lastWarningUs = now;
374 qCWarning(UBXProtocolLog).noquote() << warning;
376 if (outputOverflow) {
383 if (payload[0] == 0 && payload[1] == 0) {
384 _timeModeReadback = payload[2];
391 const auto rates = Wire::decode<UBX::CfgMsgRates>(payload);
408GPSReceiveUpdates Protocol::_decodeSurveyIn(std::span<const uint8_t> payload,
GPSDecodeContext& context)
410 const auto svin = Wire::decode<UBX::NavSvin>(payload);
411 _surveyStopped = svin.active == 0 && svin.valid == 0;
412 if (!_mode.navigation) {
417 .x = (
static_cast<double>(svin.meanX) +
static_cast<double>(svin.meanXHP) * 0.01) * 0.01,
418 .y = (
static_cast<double>(svin.meanY) +
static_cast<double>(svin.meanYHP) * 0.01) * 0.01,
419 .z = (
static_cast<double>(svin.meanZ) +
static_cast<double>(svin.meanZHP) * 0.01) * 0.01,
421 status.duration = std::chrono::seconds(svin.dur);
422 status.meanAccuracyMeters = svin.meanAcc * 1e-4;
423 status.valid = (svin.valid & 1) != 0;
424 status.active = (svin.active & 1) != 0;
426 if (svin.valid == 1 && svin.active == 0) {
427 _requests.rtcmActivation =
true;
432void Protocol::_decodeMonVer(std::span<const uint8_t> payload)
436 const auto version = Wire::decode<UBX::MonVer>(payload);
437 identity.firmware = cString(version.swVersion).toByteArray();
439 const QByteArrayView hardware = cString(version.hwVersion);
440 identity.hardware = hardware.toByteArray();
443 qCWarning(UBXProtocolLog).noquote() << QStringLiteral(
"unknown board hw: %1").arg(QString::fromUtf8(hardware));
446 for (
size_t offset = UBX::WIRE_SIZE<UBX::MonVer>; offset < payload.size();
447 offset += UBX::WIRE_SIZE<UBX::MonVerExtension>) {
448 const auto block = Wire::decode<UBX::MonVerExtension>(payload, offset);
449 const QByteArrayView extension = cString(block.extension);
451 if (
const auto at = extension.indexOf(
"FWVER="); at >= 0) {
452 const QByteArrayView field = extension.sliced(at);
453 if (field.startsWith(
"FWVER=SPG ") || field.startsWith(
"FWVER=HPS ")) {
454 identity.timeModeUnsupported =
true;
456 identity.firmware = field.sliced(6).toByteArray();
457 qCDebug(UBXProtocolLog) <<
"u-blox firmware version:" <<
identity.firmware;
463 if (
const auto at = extension.indexOf(
"PROTVER="); at >= 0) {
464 const QByteArrayView text = extension.sliced(at + 8);
465 identity.protocolVersion = text.toByteArray();
466 qCDebug(UBXProtocolLog) <<
"u-blox protocol version:" << text;
467 const std::string_view protocol(text.data(),
size_t(text.size()));
468 const auto major = NMEA::number<unsigned>(protocol.substr(0, protocol.find(
'.')));
469 if (!major || *major == 0) {
475 identity.timeModeUnsupported =
true;
479 if (
const auto at = extension.indexOf(
"MOD="); at >= 0) {
480 const QByteArrayView field = extension.sliced(at);
481 if (field.startsWith(
"MOD=NEO-M8P-0") || field ==
"MOD=NEO-M8N" || field ==
"MOD=NEO-M9N") {
482 identity.timeModeUnsupported =
true;
484 identity.model = field.sliced(4).toByteArray();
485 identity.isM8p = field.contains(
"M8P");
491 qCDebug(UBXProtocolLog) <<
"u-blox module:" <<
identity.model;
496void Protocol::_decodeSatellites(uint16_t message, std::span<const uint8_t> payload)
503 decodeSatelliteBlocks<UBX::NavSat, UBX::NavSatSatellite>(
504 payload, _satellites, [](
const auto& header) {
return header.numSvs; },
505 [&SYSTEMS](
auto& report,
const auto& satellite) {
508 (satellite.flags & 8) != 0);
512 decodeSatelliteBlocks<UBX::NavSvinfo, UBX::NavSvinfoChannel>(
513 payload, _satellites, [](
const auto& header) {
return header.numCh; },
514 [](
auto& report,
const auto& channel) {
std::optional< uint8_t > gpsSatellitesUsed(std::optional< unsigned > count)
A reported used-satellite count; receivers report UINT8_MAX and above when the count is unknown.
@ RTCM3
0xD3 frames with a valid CRC-24Q.
void publishIntegrity(GPSIntegrityReport &report)
Stamps the family's working report with the receipt time and publishes a copy.
void publishRTCM(std::span< const uint8_t > frame)
GPSStreamDemux & stream()
void publishSatellites(const GPSDecodedSatellites &report)
void publishSurvey(GPSSurveyReport &status)
Stamps status with the receipt time and publishes a copy. Survey progress is not an update flag.
void setEnabled(GPSFrameKind kind, bool enabled)
Enables or disables an available framer; either way it restarts from an empty state.
Epoch * find(uint32_t tow, GPSDecodeContext &context)
The epoch at tow, started now when new; nullptr for a time of week already published or too old.
void end(uint32_t tow, GPSDecodeContext &context)
Publishes the epoch at tow and the ones before it.
void expire(GPSDecodeContext &context)
Publishes epochs older than MAX_AGE.
void flush(GPSDecodeContext &context) override
Publishes epochs past their deadline.
bool completeReceiveCycle(GPSReceiveUpdates handled) override
Whether a receive cycle has what it waits for: a pending readback, or else a navigation update.
QString identity() const override
Model and firmware reported during configuration; empty when unknown.
GPSReceiveUpdates onFrame(const GPSFrame &frame, GPSDecodeContext &context) override
bool armNavigationDecode(GPSNavigationDecode decode, GPSDecodeContext &context) override
static constexpr std::chrono::seconds REPEATED_WARNING_INTERVAL
A receiver warning or error repeated within this interval is logged at debug level.
std::chrono::milliseconds nextReadSlice(std::chrono::milliseconds timeout) const override
Reads briefly once part of an epoch has arrived, as the rest follows back to back.
static constexpr std::array NAVIGATION_MESSAGES
bool armDecodeOnly(const GPSConfig &config, GPSDecodeContext &context) override
The decoder state a successful configure() leaves: navigation, survey and corrections decoding.
bool readbackReplyOutstanding() const
A reply to a readback settled without it can still arrive.
bool readbackReady() const
void accept(Acknowledgement response)
bool readbackPending() const
bool awaitingAcknowledgement() const
bool ratePollPending() const
constexpr double DEG_TO_RAD
GPSEllipsoidPosition fromEcef(const Ecef &position)
uint64_t utcMicroseconds(int year, int month, int day, int hour, int minute, int second, int32_t nsec)
QString outputOverflow(RTKSettings::ReceiverRole role, bool compactObservations)
The warning that the receiver produces more output than its connection carries.
constexpr uint32_t WEEK_MS
Milliseconds in a GPS week; every time of week is below it.
constexpr bool fresh(uint64_t timestampUs, uint64_t nowUs, std::chrono::microseconds lifetime)
Whether a receipt is younger than lifetime.
constexpr MessageId NAV_SVIN
constexpr MessageId ACK_NAK
constexpr MessageId MON_HW
constexpr MessageId INF_DEBUG
constexpr MessageId MON_RF
constexpr MessageId SEC_SIG
constexpr MessageId NAV_EOE
constexpr MessageId ACK_ACK
constexpr MessageId NAV_STATUS
constexpr MessageId NAV_DOP
constexpr MessageId CFG_VALGET
constexpr MessageId NAV_SAT
constexpr MessageId MON_VER
constexpr MessageId NAV_SVINFO
constexpr MessageId INF_WARNING
constexpr MessageId CFG_MSG
constexpr MessageId INF_ERROR
constexpr MessageId CFG_TMODE3
constexpr MessageId INF_NOTICE
constexpr MessageId NAV_HPPOSLLH
constexpr MessageId NAV_PVT
u-blox receivers over UBX: CFG-VALSET configuration from protocol 27, CFG-MSG and CFG-TMODE3 before i...
constexpr ReceiverProfile receiverProfile(Board board)
std::optional< ConfigurationValues > decodeConfigurationValues(std::span< const uint8_t > payload)
Decodes a CFG-VALGET response (version 1, RAM layer) of at most ConfigurationValues::MAX_KEYS values.
constexpr const MessageSchema * messageSchema(uint16_t message)
bool validPayload(uint16_t message, std::span< const uint8_t > payload, const MessageSchema *schema)
double longitudeDegrees(int32_t value)
double latitudeDegrees(int32_t value)
constexpr Board boardFromHardwareVersion(std::string_view hardwareVersion)
The generation whose MON-VER hwVersion is hardwareVersion; unknown when none matches.
constexpr size_t MAX_CONTROL_PAYLOAD_SIZE
constexpr float DOP_PER_UNIT
The receiver configuration requested for one configure() attempt.
static constexpr uint16_t SAT_INFO_MAX_SATELLITES
void addSatellite(GPSConstellation constellation, bool used)
std::array< Constellation, MAX_CONSTELLATIONS > constellations
Constellation * ensureConstellation(GPSConstellation constellation)
AntennaState
Ordered by severity, so the worst of several reports is the greatest.
static constexpr SpoofingState spoofingStateFromValue(int value)
static constexpr JammingState jammingStateFromValue(int value)
uint64_t outputOverflowUs
What a decode-only runtime decodes from a receiver configured elsewhere (GPSFamilyProtocol::armNaviga...
GPSNavigationValues::FixType FixType
GPSEllipsoidPosition position
constexpr uint16_t value() const
The CFG-MSG output rates of one message on each I/O port, as a poll returns them.
bool protocol27
Configures through CFG-VALSET unless MON-VER reports an older protocol version.