Skip to content

gps: ask u-blox receivers only for the signals and constellations they can run - #11965

Open
MrScothh wants to merge 6 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:fix/ublox-glonass-keys
Open

MrScothh wants to merge 6 commits into
iNavFlight:maintenance-10.xfrom
MrScothh:fix/ublox-glonass-keys

Conversation

@MrScothh

@MrScothh MrScothh commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Three things keep a u-blox receiver from being configured with what it can actually
run, all three in configureGNSS10() and all three ending the same way: the receiver answers
NAK, applies none of the message, and INAV quietly rewrites the user's constellation
settings to their defaults without sending them.

1. The F10 is offered keys and signals it does not have

A u-blox F10 reports hwVersion 000A0000, the same string an M10 reports, so INAV correctly identifies it as protocol 27+ and configures it through CFG-VALSET. It then sends two things that receiver cannot accept.

Its configuration database has no Glonass group at all. The F10 interface description (UBX-23002975) shows the CFG-SIGNAL group jumping straight from CFG-SIGNAL-QZSS_ENA (0x10310024) to CFG-SIGNAL-NAVIC_ENA (0x10310026): key 0x10310025, which is CFG-SIGNAL-GLO_ENA on an M10, is not there, and neither is 0x10310018.

And its Beidou L1 signal is B1C, not B1I. configureGNSS10() picks B1I unless Glonass is on, which on an F10 both enables a signal it cannot receive and turns off the one it uses. The NEO-F10N integration manual is explicit about what happens then:

For constellations with dual-band support, it is not possible to disable one of the bands. Both bands must be enabled and disabled at once. [...] Unsupported combinations are rejected with a UBX-ACK-NAK message.

A CFG-VALSET is all or nothing, so either of those makes the receiver answer NAK and apply none of the message. SBAS, Galileo, BeiDou and QZSS go down with them, and INAV then resets gps_ublox_use_galileo, gps_ublox_use_beidou and gps_ublox_use_glonass to their defaults.

This is the whole F10 family: NEO-F10N, DAN-F10N and the boards built on them. What a real NEO-F10N answers to MON-VER:

swVersion  EXT SPGL1L5 6.00 (041e8a)
hwVersion  000A0000
           FWVER=SPGL1L5 6.00
           PROTVER=40.00
           MOD=NEO-F10N
           GPS;GAL;BDS
           SBAS;QZSS
           NAVIC

GPS;GAL;BDS, with no GLO, and SPGL1L5 for the dual band firmware.

What changes for the F10

Both pieces of information are already on hand.

INAV polls UBX-MON-GNSS before the configuration starts and keeps the supported-constellation mask, and gpsUbloxHasGlonass() is already there. The two Glonass keys are last in the array, so leaving them out makes the message shorter rather than different. They are only dropped when the mask says so: an empty mask means MON-GNSS never answered, nothing is known, and every key goes out as before.

The MON-VER extensions are already being walked for the constellation list and the protocol version, so the same walk now notices L1L5. Since section 3 the Beidou choice follows the receiver's own key list, and L1L5 only counts when that read gets no answer.

Nothing changes for a receiver that reports Glonass.

2. Any receiver can be asked for more constellations than it can run

UBX-MON-GNSS also says how many major constellations the receiver can track at once,
and INAV reads that number into ubx_capabilities.capMaxGnss without ever using it.
Ask for more and the answer is the same NAK as above. The u-blox 8 protocol
description says so for UBX-CFG-GNSS:

The receiver will respond to such a request with a UBX-ACK-ACK message if it can
support the requested configuration or a UBX-ACK-NAK message if not. [...] Otherwise
the receiver will reject the request, by issuing a UBX-ACK-NAK and continuing
operation with the previous configuration.

and the M10 and F10 integration manuals say it for the configuration interface:

Unsupported combinations are rejected with a UBX-ACK-NAK message, and the warning
"inv sig cfg" is sent via UBX-INF and NMEA-TXT messages (if enabled).

This is not hypothetical on the receivers INAV supports. A NEO-M8 tracks up to three
concurrent GNSS, so INAV's own defaults, GPS with Galileo and BeiDou, already fill it:
turning GLONASS on as well is refused, and today that loses the whole constellation
configuration rather than one constellation. The M10 platform tracks four, so nothing
is trimmed there.

What happens after the NAK is the part worth fixing. INAV resets the three settings to
their defaults in its own configuration but never sends them, so the receiver keeps
running whatever it had, the configurator shows the defaults instead of the choice
that was made, and the next save of any kind persists them.

What changes there

The constellations to ask for are now worked out once, from the selection, the
constellations the receiver reports and how many it says it can run:

  • what is not in the MON-GNSS supported mask is dropped, because it cannot work
    anyway;
  • if what is left does not fit the count, the constellations INAV turns off by default
    go first: GLONASS, then BeiDou, then Galileo;
  • the settings are not touched, so the same configuration gets everything it asked for
    on a receiver that can run it;
  • the count is only used next to a fresh mask, since it is not cleared between
    detection attempts.

Both paths use it, CFG-VALSET for M10 and later and CFG-GNSS for M8 and M9, so the
M8 case above stops being a refusal and becomes three constellations that work.

3. Keys a receiver's firmware does not have, and the second band

#12174 found the next way the fixed key list breaks: an M10 with ROM SPG 5.00 has neither CFG-SIGNAL-BDS_B1C_ENA nor CFG-SIGNAL-QZSS_L1S_ENA, so it refuses every constellation VALSET, whatever the settings.

Rather than keeping a list of firmware versions, INAV now asks the receiver. Before the constellations are configured, one CFG-VALGET of the whole CFG-SIGNAL group (0x1031ffff) returns every signal key the firmware has, and the VALSET carries only those. BeiDou uses B1I where B1C is missing. If the read gets no answer, the list goes out as before.

On the F10 I measured which combinations it takes, through serialpassthrough and on the RAM layer only, with every key put back afterwards:

CFG-VALSET answer
a constellation off, its bands in any state ACK
Galileo on, E1 on, E5a off NAK
Galileo on, E1 and E5a on ACK
BeiDou on as B1C + B2a ACK
BeiDou on as B1C alone, B1I + B2a, B1I + B1C + B2a or B1I alone NAK
GPS or QZSS on with L5 off NAK

A constellation that is on needs all its bands. So the second band of Galileo, BeiDou and QZSS (E5a, B2a, L5) now goes on and off with the first, only where the receiver has the key, and a receiver that has B2a gets BeiDou as B1C + B2a. Before, a module whose E5a or B2a had been turned off elsewhere could not get Galileo or BeiDou back.

If the receiver still refuses, the constellations are sent alone and it picks their signals. Either way the settings stay as saved. What counts is what the receiver then runs, not its ACK: on the F10 with E5a off, the enable keys alone got an ACK and Galileo still did not run. So status now compares the settings with the next MON-GNSS answer, which INAV already polls every 5 s, and prints The receiver runs other constellations than the settings when they differ. The lines above it show which.

Testing

SITL on Windows against an emulated u-blox that answers MON-VER and MON-GNSS and streams NAV-PVT. The F10 emulator applies its manual's rules and NAKs the message if it sees a Glonass key, B1I enabled, or B1C turned off while B2a is on. What reaches the receiver in the constellation VALSET:

emulated receiver keys Glonass keys Beidou L1 NAK fix
M10 12 2 B1I 0 3D
M10, gps_ublox_use_glonass = ON 12 2 B1C 0 3D
F10 10 0 B1C 0 3D
F10, gps_ublox_use_glonass = ON 10 0 B1C 0 3D

Before the change the two F10 cases were rejected, the first for both reasons and the second for the Glonass keys alone, and no part of the constellation configuration reached the receiver. The two M10 cases are byte for byte what they were.

For the second fix, with the emulated receiver refusing anything past its reported
limit, as the manuals describe:

emulated receiver selected before after
M10 reporting 3, all four selected GPS+GAL+BDS+GLO NAK, gps_ublox_use_glonass rewritten to OFF runs GPS+GAL+BDS, settings untouched
M10 reporting 2, all four selected GPS+GAL+BDS+GLO NAK, settings rewritten runs GPS+GAL
M8 reporting 3, all four selected GPS+GAL+BDS+GLO NAK, settings rewritten runs GPS+GAL+BDS
M10 reporting 4, all four selected GPS+GAL+BDS+GLO all four all four, unchanged
F10 reporting 3 GPS+GAL+BDS+GLO (see table above) runs GPS+GAL+BDS, GLONASS was never possible

Also checked, because the capability state outlives a protocol restart: an M10 that goes quiet long enough for the state machine to restart, then comes back with its MON-GNSS reply lost, still gets all 12 keys, exactly as it does on maintenance-10.x.

On hardware since (comment of 2026-10-06): a NEO-F10N on a TBS Lucid H7 Wing and a SAM-M10Q on a Holybro Kakute H7 Mini, both read back with CFG-VALGET. The F10 takes the constellation VALSET and follows gps_ublox_use_beidou (on maintenance-10.x a saved OFF comes back ON at the next boot); the M10 runs its defaults and keeps GLONASS ON with BeiDou OFF. Still worth a check: an M8 with all four constellations selected.

Signal keys and second band. On the bench Lucid with the NEO-F10N, the CFG-VALGET of the group returns 19 keys. With E5a first turned off in the receiver:

gps_ublox_use_galileo without the second band with it
ON first VALSET NAK, enable keys alone ACK, status Galileo 0/1 with the setting ON first VALSET ACK (13 pairs), Galileo 1/1
OFF, then ON again ACK at the first try both times, Galileo 0/1 then 1/1

A change made in the receiver behind INAV's back (Galileo turned off through passthrough) does not last: the GPS times out during the passthrough, INAV configures it again, and status shows Galileo 1/1 without the line.

SITL against the emulated receivers, the F10 now with the rules above and an SPG 5.00 that, as measured in #12174, takes B1I next to Glonass and does not run BeiDou:

emulated receiver constellations set VALSET status line
F10 GAL, BDS ACK, 13 pairs no
F10 with E5a off GAL, BDS ACK, 13 pairs no
F10 refusing the group read GAL, BDS ACK, 13 pairs no
M10 GAL, BDS, GLO ACK, 12 pairs no
M10 SPG 5.00 GAL, BDS, GLO ACK, 10 pairs yes, BeiDou 0/1
M10 SPG 5.00 GAL, BDS ACK, 10 pairs no
receiver refusing every VALSET GAL off NAK, then NAK yes, Galileo 1/1

Not tested on hardware: an M10 with SPG 5.00 (asked in #12174), and an M10 since this change.

Overlaps

#11979 edits the same MON-VER extension loop: this PR looks for L1L5 in it, #11979 for the module name and for SBAS, QZSS and NavIC, and it also stops the loop at the last whole 30-byte line. Whichever of the two is merged second needs a rebase that keeps both checks in one loop; I'll do it. With #11999 only the declarations at the top of gps_ublox.c touch.

Since the signal key read, #12021 and #12083 also meet this PR in gps_ublox_utils.c and the unit test file, #12091 in the unit test file, and #11895 in the message enum and at the end of the constellation step, all where both sides add code at the same place. If this PR lands second I'll rebase it; if it lands first I can post the resolution for the other one.

@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Avoid unsupported GLONASS keys on u-blox F10 receivers

🐞 Bug fix 🕐 Less than 10 minutes

Grey Divider

AI Description

• Omits Glonass CFG-VALSET keys when MON-GNSS confirms Glonass is unavailable.
• Preserves existing configuration when capabilities are unknown or Glonass is supported.
• Prevents F10 receivers rejecting unrelated SBAS, Galileo, BeiDou, and QZSS settings.
Diagram

graph TD
    A["MON-GNSS Poll"] --> B["Capability Mask"] --> C{"Glonass known absent?"}
    C -- Yes --> D["10-key VALSET"] --> F["Receiver Config"]
    C -- No --> E["12-key VALSET"] --> F
Loading
High-Level Assessment

The chosen approach is optimal: it reuses the authoritative MON-GNSS capability response and safely truncates trailing Glonass entries without duplicating payload definitions. Detecting F10 models from version strings would be more brittle, while always omitting Glonass would regress supported M10 receivers.

Files changed (1) +7 / -2

Bug fix (1) +7 / -2
gps_ublox.cExclude unsupported Glonass keys from CFG-VALSET +7/-2

Exclude unsupported Glonass keys from CFG-VALSET

• Checks the previously populated MON-GNSS capability state before configuring protocol 27+ receivers. When capabilities explicitly report no Glonass support, the payload omits the final two Glonass keys; otherwise, the existing 12-key behavior remains unchanged.

src/main/io/gps_ublox.c

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Receiver restarts skip Glonass setup ✓ Resolved
Description
noGlonass treats a nonzero capMaxGnss as proof of a current capability response, but the
initialization path clears supported without clearing the previously cached capMaxGnss. After
communication loss restarts the protocol, the capability wait therefore completes immediately with
an empty support mask and configureGNSS10() omits both Glonass keys from a Glonass-capable
receiver.
Code

src/main/io/gps_ublox.c[478]

+        const bool noGlonass = (gpsUbloxMaxGnss() != 0) && !gpsUbloxHasGlonass();
Evidence
The protocol initialization clears only supported, enabledGnss, and defaultGnss, while its
wait condition uses capMaxGnss; gpsRestartUBLOX() reruns this initialization after the GPS state
machine detects lost communication. Since the added predicate combines the stale nonzero maximum
with the newly cleared support mask, it evaluates noGlonass as true before the fresh MON-GNSS
reply is processed.

src/main/io/gps_ublox.c[1269-1277]
src/main/io/gps_ublox.c[1319-1331]
src/main/io/gps.c[591-617]
src/main/io/gps_ublox.c[178-181]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Glonass-key decision can consume mismatched capability state after a GPS protocol restart because `supported` is cleared while `capMaxGnss` remains nonzero from the previous session. This makes configuration proceed before the new MON-GNSS response and incorrectly classify the receiver as lacking Glonass.
## Fix Focus Areas
- src/main/io/gps_ublox.c[1269-1277]
- src/main/io/gps_ublox.c[478-480]
## Recommended Fix
Reset `ubx_capabilities.capMaxGnss` alongside the three capability masks before polling MON-GNSS. This restores the intended wait for a fresh response while retaining the existing fallback that sends all keys if polling exhausts its retries.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can open the rule or relationship behind a finding in one click

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/io/gps_ublox.c Outdated
@MrScothh
MrScothh force-pushed the fix/ublox-glonass-keys branch from cc20741 to 42e8c23 Compare September 17, 2026 21:19
@MrScothh

Copy link
Copy Markdown
Contributor Author

Good catch, and it reproduces.

Measured in SITL with an emulated M10 that answers everything, then goes silent long enough for the protocol to restart, then comes back but loses the one MON-GNSS reply. The constellation VALSET that reaches the receiver after the restart:

build keys of which Glonass
maintenance-10.x 12 2
this PR as first pushed 10 0

So the report is right. capMaxGnss survives the restart while the masks are cleared, the wait passes on the stale value, and the predicate then read an empty mask as "no Glonass".

I did not take the suggested fix, though. Clearing capMaxGnss with the masks does remove the mismatch, but it also changes what happens when that reply goes missing: the wait times out and the state machine restarts instead of configuring. Same scenario, measured:

build keys of which Glonass
this PR with capMaxGnss cleared 0, no constellation VALSET at all 0

That trades one regression for a worse one.

The decision now reads the support mask directly, which is cleared in the same place the other masks are:

const bool noGlonass = ubx_capabilities.supported && !gpsUbloxHasGlonass();

An empty mask means MON-GNSS never answered, so nothing is known and every key goes out, exactly as before this PR. No new state, no change to the wait, no change in timing.

After the change:

scenario keys of which Glonass
M10, MON-GNSS answers 12 2
F10, MON-GNSS without Glonass 10 0
F10 with gps_ublox_use_glonass = ON 10 0
restart, MON-GNSS answers 12 2
restart, MON-GNSS reply lost 12 2

Branch force pushed.

@MrScothh
MrScothh force-pushed the fix/ublox-glonass-keys branch from 42e8c23 to e8f77e1 Compare September 17, 2026 21:39
@MrScothh MrScothh changed the title gps: do not send Glonass keys to a receiver that has none gps: configure u-blox F10 receivers with the signals they have Sep 17, 2026
@MrScothh MrScothh changed the title gps: configure u-blox F10 receivers with the signals they have gps: ask u-blox receivers only for the signals and constellations they can run Sep 19, 2026
@sensei-hacker sensei-hacker added this to the 10.1 milestone Sep 20, 2026
An F10 reports the M10 hardware version, so INAV takes the CFG-VALSET path
for it, and then sends two things it cannot accept. Its configuration
database has no Glonass group at all, and its Beidou L1 signal is B1C, not
the B1I that INAV picks by default. Either one makes the receiver reject the
whole message, so SBAS, Galileo, BeiDou and QZSS never get applied.

UBX-MON-GNSS is already polled before the configuration starts and reports
which constellations the receiver has, so leave the Glonass keys out when it
says there are none. An empty mask means MON-GNSS never answered, and then
nothing is known, so every key goes out as before.

The dual band receivers name themselves in the MON-VER extensions, which are
already being read for the constellation list, so pick B1C there. It is what
they have, and their two bands cannot be configured apart.
UBX-MON-GNSS says how many major constellations the receiver can track at
once, and INAV read the number without using it. Asking for more gets the
configuration refused: the receiver answers NAK and keeps what it had, as the
M8 protocol description puts it for UBX-CFG-GNSS. INAV then reset Galileo,
BeiDou and GLONASS to their defaults in its own settings without sending them,
so the receiver ran one configuration, the configurator showed another, and
the next save of any kind replaced the user's choice with the defaults.

The constellations to enable are now worked out once, from the selection, the
constellations the receiver has and how many it can take. When the selection
does not fit, the ones INAV leaves off by default go first: GLONASS, then
BeiDou, then Galileo. The settings are not touched, so the same configuration
gets everything it asked for on a receiver that can run it. Both the
CFG-VALSET path for M10 and later and the CFG-GNSS path for M8 and M9 use it.

The count is only used next to a fresh MON-GNSS mask, since it is not cleared
between detection attempts.
@MrScothh
MrScothh force-pushed the fix/ublox-glonass-keys branch from df62a3e to 1e25a27 Compare September 20, 2026 09:20
@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

RAM / Flash usage vs. base commit 13ebed9 — commit 6b689ed

Target Flash Δ RAM Δ
MATEKF405 +1344 B (+0.19%) CCM: ±0 B (±0.00%)
RAM: +8 B (+0.01%)
MATEKF722 +712 B (+0.15%) ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
TCM: ±0 B (±0.00%)
MATEKF765 +1296 B (+0.17%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: +16 B (+0.01%)
MATEKH743 +1264 B (+0.16%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: ±0 B (±0.00%)
RAM: +32 B (+0.02%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Test firmware build ready — commit 6b689ed

Download firmware for PR #11965

251 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

The restart cleared the capability mask but not capMaxGnss, so after a
communication loss the wait for MON-GNSS ended at once on the old count,
with an empty mask. The GNSS keys then went out as if nothing was known,
GLONASS ones included, which an F10 answers with the NAK this PR avoids.
@MrScothh

MrScothh commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Tested today on a real NEO-F10N (SPGL1L5 6.00, PROTVER 40.00) on a TBS Lucid H7 Wing, this PR merged on the current maintenance-10.x against that base alone, reading the receiver's RAM back with CFG-VALGET through serialpassthrough:

maintenance-10.x with this PR
gps_ublox_use_beidou = OFF, saved comes back ON at the next boot: the F10 NAKs the constellation VALSET (GLONASS keys it does not have, B1I) and gpsConfigure() resets the three use_* settings to their defaults stays OFF; the receiver reads BDS_ENA 0, B1C 0, MON-GNSS active mask 0x0D -> 0x09, fix kept
defaults (galileo ON, beidou ON, glonass OFF) GPS 1, GAL 1 with E1 1, BDS 1 with B1C 1 and B1I 0, QZSS 1, SBAS 1; the GLONASS keys answer NAK on this receiver, as expected

An M10 (SPG 5.10, PROTVER 34.09) on a Holybro Kakute H7 Mini with the same merged image, read back the same way:

M10 receiver reads
defaults (galileo ON, beidou ON, glonass OFF) GPS 1, GAL 1 with E1 1, BDS 1 with B1I 1 and B1C 0, GLO 0, QZSS 1, SBAS 0; MON-GNSS active mask 0x0D; messages every 120 ms, 0 errors, 0 timeouts
gps_ublox_use_glonass = ON, gps_ublox_use_beidou = OFF, saved both kept at the next boot; the receiver reads GLO 1 with L1 1, BDS 0, active mask 0x0B; 0 errors, 0 timeouts

The same M10 on a Flywoo F722 Pro V2 keeps a 10 Hz fix with the PR (0 errors, 0 timeouts). M8 and M9 not tested here.

Before configuring the constellations, one CFG-VALGET of the whole
CFG-SIGNAL group (0x1031ffff) asks the receiver which keys it knows, and the
CFG-VALSET is built from those only. A key the firmware lacks makes the
receiver refuse the whole message: the M10 with ROM SPG 5.00 (PROTVER 34.00)
has neither BDS_B1C_ENA nor QZSS_L1S_ENA (iNavFlight#12174), and the F10 has no Glonass
group. BeiDou uses B1I where B1C is missing. If the read gets no answer, the
whole list goes out as before.

On a dual band receiver the second band of Galileo, BeiDou and QZSS (E5a,
B2a, L5) now goes on and off with the first. The F10 refuses a constellation
turned on with one of its bands off, so on a module where E5a or B2a had been
turned off, turning Galileo or BeiDou on was refused. It also takes BeiDou
only as B1C + B2a: a receiver that has the B2a key gets that, instead of
relying on the "L1L5" string of MON-VER.

If the receiver still refuses, a combination it doesn't take, the
constellations are sent alone and it picks their signals. Either way the
user's settings stay as saved instead of being reset to their defaults.
What counts is what the receiver runs, not its ACK: an F10 with E5a off took
Galileo's enable key alone and still didn't run Galileo. So `status` compares
the settings with the next MON-GNSS answer, which INAV already polls every
5 s, and says when the receiver runs other constellations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants