<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: UAV GNSS</title>
    <description>The latest articles on DEV Community by UAV GNSS (@uavgnss6bot).</description>
    <link>https://dev.to/uavgnss6bot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4105630%2Ff7a4c18b-0e6f-4095-a15a-bc9284f6cf31.png</url>
      <title>DEV Community: UAV GNSS</title>
      <link>https://dev.to/uavgnss6bot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uavgnss6bot"/>
    <language>en</language>
    <item>
      <title>mosaic-X5 vs u-blox F9P for ArduPilot: what you actually get for the extra cost</title>
      <dc:creator>UAV GNSS</dc:creator>
      <pubDate>Mon, 05 Oct 2026 01:32:51 +0000</pubDate>
      <link>https://dev.to/uavgnss6bot/mosaic-x5-vs-u-blox-f9p-for-ardupilot-what-you-actually-get-for-the-extra-cost-16g4</link>
      <guid>https://dev.to/uavgnss6bot/mosaic-x5-vs-u-blox-f9p-for-ardupilot-what-you-actually-get-for-the-extra-cost-16g4</guid>
      <description>&lt;p&gt;We're &lt;strong&gt;UAV GNSS&lt;/strong&gt;, a Septentrio receiver maker — we build HB-series receivers around Septentrio silicon. "Is a mosaic-X5 worth it over an F9P?" is the comparison ArduPilot builders bring to us most often, so here's an honest breakdown, including the cases where the F9P is the right answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they're the same
&lt;/h2&gt;

&lt;p&gt;Both are multi-band (L1/L2) GNSS receivers that produce RTK-fixed centimetre positions. Both speak standard interfaces: NMEA out for position, RTCM v3 in for corrections, and a raw-log protocol you can record and post-process (UBX on u-blox, SBF on Septentrio). ArduPilot supports both natively.&lt;/p&gt;

&lt;p&gt;For a low-dynamics airframe with a clean sky view and a healthy NTRIP link, an F9P does the job at a fraction of the cost. That is not a knock on it — it's the correct answer for a large share of builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they actually differ
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;u-blox ZED-F9P class&lt;/th&gt;
&lt;th&gt;Septentrio mosaic-X5 class&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Native navigation rate&lt;/td&gt;
&lt;td&gt;~10–20 Hz&lt;/td&gt;
&lt;td&gt;up to 100 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-band interference suppression&lt;/td&gt;
&lt;td&gt;~25 dB class, usually unpublished&lt;/td&gt;
&lt;td&gt;AIM+ ≈ 40–60 dB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multipath mitigation&lt;/td&gt;
&lt;td&gt;basic&lt;/td&gt;
&lt;td&gt;APME+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dual-antenna GNSS heading&lt;/td&gt;
&lt;td&gt;no (single antenna)&lt;/td&gt;
&lt;td&gt;yes (mosaic-H / mosaic-G5 P3H class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation-message authentication&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;OSNMA-ready (Galileo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free cm-class corrections, no base&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Galileo HAS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Update rate, and why "native" matters
&lt;/h2&gt;

&lt;p&gt;F9P-class modules typically publish a navigation solution at 10–20 Hz. mosaic-X5 computes solutions at up to &lt;strong&gt;100 Hz&lt;/strong&gt;, and each epoch is derived directly from the GNSS measurements instead of being interpolated between them.&lt;/p&gt;

&lt;p&gt;For a slow tractor that difference is invisible. For a fast airframe, or for a control loop that wants fresh data every cycle, it's the difference between steering on a current position and steering on an extrapolation. It also changes what your logs look like when you analyse drops — we walk through SBF fix-quality and RTK-drop forensics in &lt;a href="https://uav-gnss.com/satellite-count-drop-rtk-fixed-sbf-analyzer-guide/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interference immunity is where the money actually goes
&lt;/h2&gt;

&lt;p&gt;AIM+ suppresses roughly &lt;strong&gt;40–60 dB&lt;/strong&gt; of in-band interference. Consumer modules sit in the &lt;strong&gt;~25 dB&lt;/strong&gt; class and frequently publish no figure at all. Near power lines, electric fences, 4G/5G sites — or anyone deliberately jamming — that gap decides whether you hold RTK fixed or never see it lock.&lt;/p&gt;

&lt;p&gt;Symptoms that you are interference-limited rather than correction-limited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the fix drops to float at the &lt;em&gt;same physical location&lt;/em&gt; every pass;&lt;/li&gt;
&lt;li&gt;SNR fades on satellites at a specific azimuth rather than a specific elevation;&lt;/li&gt;
&lt;li&gt;the receiver loses lock near pylons, electric fences or mobile masts;&lt;/li&gt;
&lt;li&gt;correction age stays low and steady while the fix still degrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AIM+ runs continuously and needs no configuration. If your build works on a farm, near infrastructure, or anywhere you don't control the RF environment, this is usually the real reason to step up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heading without a magnetometer
&lt;/h2&gt;

&lt;p&gt;A single-antenna build has no direct heading — the autopilot fuses GPS velocity and the compass, and the compass is usually the weak link: motor currents, steel frames and fences bend the local field by 5–30°, and a bad heading steers the EKF into the wrong row.&lt;/p&gt;

&lt;p&gt;A mosaic receiver with two antenna inputs measures carrier-phase &lt;strong&gt;heading across a physical baseline&lt;/strong&gt;, so it is immune to magnetic interference and doesn't drift. In ArduPilot you wire the second antenna to a second UART and enable a second GPS rather than swapping the receiver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interfaces — and one myth worth killing
&lt;/h2&gt;

&lt;p&gt;mosaic-X5 exposes UART (LVTTL, 3.3 V), USB, SPI and I²C. It does &lt;strong&gt;not&lt;/strong&gt; have native CAN or J1939 — and no Septentrio chip does. CAN is a carrier- or box-level feature, driven by the carrier's own processor and transceiver. If you need a machine bus, pick a box that provides it or add a transceiver/gateway; don't buy a bare module expecting native CAN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameter starting points
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ArduPilot&lt;/strong&gt; (&lt;code&gt;GPS_TYPE=9&lt;/code&gt; selects the Septentrio driver):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Septentrio (SBF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_BAUD_RATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;115200 baud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_RATE_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;10 Hz update (lower = faster, if your link supports it)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep &lt;code&gt;GPS_AUTO_CONFIG=1&lt;/code&gt; so the receiver is configured on every boot. For dual-antenna heading, enable the second receiver on the secondary port (&lt;code&gt;GPS2_TYPE=9&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PX4:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_GNSS_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Septentrio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_PROTOCOL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Septentrio SBF / NMEA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where it lands
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;F9P&lt;/strong&gt; — low-dynamics, clean RF, budget-sensitive. Correct choice, and we'll say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mosaic-X5&lt;/strong&gt; — when a dropped fix costs a mission, a pass or a flight: high dynamics, contested RF, or you need heading, navigation-message authentication, or free HAS corrections with no base.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wiring diagrams, full parameter tables and an SBF log analyzer with RTK-drop forensics are in our integration guide: &lt;strong&gt;&lt;a href="https://github.com/uavgnss6-bot/septentrio-gnss-integration-guide" rel="noopener noreferrer"&gt;github.com/uavgnss6-bot/septentrio-gnss-integration-guide&lt;/a&gt;&lt;/strong&gt;. Receiver lineup and AIM+ details: &lt;a href="https://uav-gnss.com/product-category/gnss-receiver/" rel="noopener noreferrer"&gt;uav-gnss.com/product-category/gnss-receiver/&lt;/a&gt; and &lt;a href="https://uav-gnss.com/aim-resilient-gnss/" rel="noopener noreferrer"&gt;uav-gnss.com/aim-resilient-gnss/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which failure hurts you more today — losing RTK in a noisy spot, or not having enough update rate? That answer usually decides it.&lt;/p&gt;

</description>
      <category>gnss</category>
      <category>uav</category>
      <category>robotics</category>
      <category>embedded</category>
    </item>
    <item>
      <title>Ionospheric scintillation: the GNSS failure that arrives after sunset</title>
      <dc:creator>UAV GNSS</dc:creator>
      <pubDate>Thu, 24 Sep 2026 03:10:40 +0000</pubDate>
      <link>https://dev.to/uavgnss6bot/ionospheric-scintillation-the-gnss-failure-that-arrives-after-sunset-1md1</link>
      <guid>https://dev.to/uavgnss6bot/ionospheric-scintillation-the-gnss-failure-that-arrives-after-sunset-1md1</guid>
      <description>&lt;h1&gt;
  
  
  Ionospheric scintillation: the GNSS failure that arrives after sunset
&lt;/h1&gt;

&lt;p&gt;It is 20:40 at a site 15° from the equator — Brazil, Nigeria, India, Singapore, northern Australia all behave the same way. The RTK fix that held 1 cm all afternoon starts wandering. Then it drops to float. Then a couple of satellites disappear entirely. Tomorrow morning everything will be perfect again.&lt;/p&gt;

&lt;p&gt;Nothing has changed in the hardware, the corrections or the sky view. What changed is the ionosphere. If your deployments include low-latitude sites, this is one of the few GNSS error sources that is &lt;em&gt;predictable in its rhythm and unpredictable in its timing&lt;/em&gt; — and it is worth knowing how it works before it shows up in a customer's log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signal path goes through a charged layer
&lt;/h2&gt;

&lt;p&gt;GNSS signals travel roughly 20,000 km to reach the antenna, and for most of that journey there is nothing in the way. The last 1,000 km are the exception. The ionosphere — the charged layer between about 100 km and 1,000 km altitude — refracts and diffracts the signal, which produces both a delay and a distortion.&lt;/p&gt;

&lt;p&gt;When the ionised particles are smoothly distributed, a receiver can model their effect on the signal. The trouble starts when the ionosphere becomes irregular: local fluctuations in electron density distort the phase and amplitude of the signal. That is &lt;strong&gt;ionospheric scintillation&lt;/strong&gt;, and it is usually described with two indices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S4&lt;/strong&gt; — the amplitude scintillation index;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;σφ&lt;/strong&gt; (sigma-phi) — the phase scintillation index.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2s9zwi4in4xymfjask0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2s9zwi4in4xymfjask0.png" alt="Amplitude scintillation index S4 and phase scintillation index over time" width="322" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1 — S4 and σφ through a scintillation event. The usual "strong" thresholds are S4 &amp;gt; 0.6 and σφ &amp;gt; 0.3. Image courtesy of Septentrio.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where and when scintillation happens
&lt;/h2&gt;

&lt;p&gt;Scintillation events are most frequent and most intense near the &lt;strong&gt;geomagnetic equator&lt;/strong&gt;, with a weaker occurrence at the poles. They are also documented at mid-latitudes — Western Europe and the United States included — so this is not exclusively a tropical problem.&lt;/p&gt;

&lt;p&gt;Timing is driven by the sun. Solar activity follows an &lt;strong&gt;11-year cycle&lt;/strong&gt; measured by sunspot count: around solar maximum, frequent solar flares release bursts of high-energy protons and X-rays that interact with the atmosphere, and scintillation events become more common. On top of the solar cycle there is a strong daily pattern — &lt;strong&gt;sunset triggers a sharp increase in ionospheric activity that can last for hours&lt;/strong&gt;. That daily pattern is why scintillation so often looks like a "the fix degrades in the evening" bug rather than an environmental one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14x3q5ei761tmvxus17m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14x3q5ei761tmvxus17m.jpg" alt="World map of frequent high-S4 scintillation events" width="356" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2 — Distribution of high-S4 scintillation events: densest in the equatorial band, weaker at mid and high latitudes. Image courtesy of Septentrio.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpa8nv03x7kqhqh1uj00.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpa8nv03x7kqhqh1uj00.jpg" alt="24-hour S4 scintillation indices observed in Brazil" width="381" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3 — S4 recorded by a static PolaRxS receiver in Brazil (22°S) over 24 hours: activity climbs after sunset and stays elevated for hours. Image courtesy of Septentrio.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does to a GNSS solution
&lt;/h2&gt;

&lt;p&gt;For a standard GNSS receiver the failure escalates in stages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scintillation severity&lt;/th&gt;
&lt;th&gt;What the receiver does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mild&lt;/td&gt;
&lt;td&gt;Position accuracy degrades by several metres&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stronger&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Cycle slips&lt;/strong&gt; — the carrier-phase measurement jumps, and RTK has to recover ambiguity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extreme&lt;/td&gt;
&lt;td&gt;Complete loss of signal lock; even ordinary radio communication is disturbed in the same window&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the corruption is in the signal itself, not in the satellite geometry, adding constellations does not fix it — the affected measurements have to be identified and excluded, or handled with a receiver that is designed for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How IONO+ handles it
&lt;/h2&gt;

&lt;p&gt;Septentrio developed &lt;strong&gt;IONO+&lt;/strong&gt; as a direct result of working on projects in Brazil, one of the countries most affected by scintillation. Receivers with IONO+ keep tracking satellites under conditions that disrupt a standard receiver, and they identify scintillation events so their effect on position accuracy can be limited. In the static test below, the algorithm correctly identified the scintillation-affected signals and removed them from the position computation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5zxz49hhu1r5j5o1q57h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5zxz49hhu1r5j5o1q57h.png" alt="Height of a static receiver with and without scintillation-improved positioning" width="357" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4 — Height computed by a static PolaRxS receiver during scintillation: standard positioning (blue) versus scintillation-improved positioning (green). Image courtesy of Septentrio.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second half of the design is about baseline logistics. With standard RTK you normally need a &lt;strong&gt;reference station network&lt;/strong&gt; to interpolate the ionospheric delay and compensate for it at the rover. With IONO+ the ionospheric delay is &lt;strong&gt;estimated inside the receiver&lt;/strong&gt;, so no network is required: a single reference station at a baseline of up to &lt;strong&gt;40 km is enough, and up to 80 km while the ionosphere is quiet&lt;/strong&gt;. For integrators working in equatorial regions, that is often the difference between "RTK needs a dense CORS network we do not have" and "one base station on the site".&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical checklist for low-latitude deployments
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log the evidence, not just the symptom.&lt;/strong&gt; Record C/N0 per band and the receiver's interference/scintillation indicators alongside the position log. A dusk-onset pattern is the signature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test at the right hour.&lt;/strong&gt; A site that passes commissioning at 14:00 may fail at 20:00. Plan an evening test during solar maximum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the correction architecture.&lt;/strong&gt; If the site relies on network RTK interpolation, confirm what happens when the ionosphere is disturbed — this is where a receiver-side estimate changes the requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect cycle slips, not just accuracy loss.&lt;/strong&gt; Design the recovery path: how fast does your RTK engine re-converge, and what does the autopilot or controller do in the meantime?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick receivers on capability, not constellation count.&lt;/strong&gt; Multi-frequency tracking, robust tracking loops and scintillation handling matter more at these latitudes than one extra constellation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hardware that ships with this capability
&lt;/h2&gt;

&lt;p&gt;IONO+ is receiver behaviour, not a service. It is part of the Septentrio feature set on the modules and receivers used in drones, robotics and reference stations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://uav-gnss.com/product/septentrio-mosaic-x5/" rel="noopener noreferrer"&gt;Septentrio mosaic-X5 module&lt;/a&gt; — multi-frequency, all-constellation core for integrators that need raw observables;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uav-gnss.com/product/hbev322-hbev322h-compact-rtk-gnss-receiver-septentrio-mosaic-g5-p3h/" rel="noopener noreferrer"&gt;HBEV322 / HBEV322H compact RTK GNSS receiver&lt;/a&gt; — mosaic-G5 P3H with dual-antenna heading;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uav-gnss.com/product/hb52h-hb52-ultralight-rtk-gnss-module-septentrio-mosaic-g5/" rel="noopener noreferrer"&gt;HB52H / HB52 ultralight RTK module&lt;/a&gt; — few-gram mosaic-G5 for mass-limited platforms;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uav-gnss.com/product/hb10-dual-antenna-rtk-gnss-receiver-septentrio-asterx-m3-pro/" rel="noopener noreferrer"&gt;HB10 dual-antenna RTK receiver&lt;/a&gt; — AsteRx-m3 Pro+ where attitude is needed as well as position;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uav-gnss.com/product/hb62-rugged-gnss-reference-station/" rel="noopener noreferrer"&gt;HB62 rugged GNSS reference station&lt;/a&gt; — a turnkey base for a shared correction source, including the 40–80 km baseline cases above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to work out whether scintillation is what you are seeing, the practical next step is a log review: send us a capture with the C/N0 and position stream and we will read it with you — &lt;a href="https://uav-gnss.com/request-quote/" rel="noopener noreferrer"&gt;request a quote&lt;/a&gt; or &lt;a href="https://uav-gnss.com/contact/" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: Septentrio technical note on ionospheric scintillation and IONO+; S4 observations from V. V. Sreeja et al., J. Space Weather Space Clim. 1 (2011); CIGALA project data (Brazil); Septentrio product documentation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gnss</category>
      <category>gps</category>
      <category>embedded</category>
      <category>robotics</category>
    </item>
    <item>
      <title>Debugging RTK GNSS integration: ports, baud rates and protocol direction (ArduPilot / PX4)</title>
      <dc:creator>UAV GNSS</dc:creator>
      <pubDate>Mon, 21 Sep 2026 01:34:14 +0000</pubDate>
      <link>https://dev.to/uavgnss6bot/debugging-rtk-gnss-integration-ports-baud-rates-and-protocol-direction-ardupilot-px4-3n09</link>
      <guid>https://dev.to/uavgnss6bot/debugging-rtk-gnss-integration-ports-baud-rates-and-protocol-direction-ardupilot-px4-3n09</guid>
      <description>&lt;p&gt;We build Septentrio-powered GNSS receivers at &lt;a href="https://uav-gnss.com/product-category/gnss-receiver/" rel="noopener noreferrer"&gt;UAV GNSS&lt;/a&gt; — so the message &lt;em&gt;"I wired it up, the port is open, and the autopilot still says no fix"&lt;/em&gt; is the one we get most often. In our experience it is almost never the receiver. It is a &lt;strong&gt;port, baud, or protocol-direction&lt;/strong&gt; mismatch, and all three look identical from the outside.&lt;/p&gt;

&lt;p&gt;Here is the checklist we walk integrators through, plus a small script that tells you &lt;em&gt;which&lt;/em&gt; of those failure modes you are actually in instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Count the serial ports before you plan the build
&lt;/h2&gt;

&lt;p&gt;The port count is a property of the receiver, not of the connector you plug in. Counted from the official interface maps:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Receiver&lt;/th&gt;
&lt;th&gt;GNSS engine&lt;/th&gt;
&lt;th&gt;Serial ports&lt;/th&gt;
&lt;th&gt;Other links&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HB10&lt;/td&gt;
&lt;td&gt;AsteRx-m3 Pro+&lt;/td&gt;
&lt;td&gt;3x UART (LVTTL, 115200 bps - 4 Mbps)&lt;/td&gt;
&lt;td&gt;USB-C, microSD, 1PPS out, EVENT in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HB59&lt;/td&gt;
&lt;td&gt;AsteRx-m3 Pro+&lt;/td&gt;
&lt;td&gt;3x UART (LVTTL)&lt;/td&gt;
&lt;td&gt;100M Ethernet, USB-C, microSD, 1PPS, EVENT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HB6 / HB6 Pro&lt;/td&gt;
&lt;td&gt;mosaic-X5&lt;/td&gt;
&lt;td&gt;2x UART&lt;/td&gt;
&lt;td&gt;USB (4G on Pro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EV322 / HB51 / HB52&lt;/td&gt;
&lt;td&gt;mosaic-G5&lt;/td&gt;
&lt;td&gt;2x UART (TTL) + I2C&lt;/td&gt;
&lt;td&gt;PPS, EVENT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HB3 (machine-control box)&lt;/td&gt;
&lt;td&gt;AsteRx-m3 Pro+&lt;/td&gt;
&lt;td&gt;SER M12&lt;/td&gt;
&lt;td&gt;CAN (PWR) M12 x2, Ethernet M12, 4G LTE, UHF, WiFi/BT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things worth internalising immediately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These UARTs are LVTTL (3.3 V logic) at the module.&lt;/strong&gt; They are not RS-232, RS-422 or RS-485. Feeding a true RS-232 source straight in will not work and can damage the port — use the matching level shifter on the carrier board, or an external transceiver if you genuinely need RS-232.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no native CAN/J1939 on the GNSS chip.&lt;/strong&gt; CAN exists at product level (the HB3 above); on modules it is an external gateway/transceiver job. If a datasheet skim told you otherwise, you were reading the box, not the chip.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. One port per job — the three-stream rule
&lt;/h2&gt;

&lt;p&gt;Serial links usually fail because two logical jobs were pushed onto one port. Budget a port per job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Typical rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UART 1&lt;/td&gt;
&lt;td&gt;Autopilot position/status&lt;/td&gt;
&lt;td&gt;NMEA 0183 &lt;strong&gt;out&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;5-10 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UART 2&lt;/td&gt;
&lt;td&gt;Corrections &lt;strong&gt;in&lt;/strong&gt; (+ optional NMEA mirror out)&lt;/td&gt;
&lt;td&gt;RTCM v3 / CMR &lt;strong&gt;in&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;as broadcast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UART 3 (HB10/HB59)&lt;/td&gt;
&lt;td&gt;Raw logging, ROS driver, radio&lt;/td&gt;
&lt;td&gt;SBF &lt;strong&gt;out&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;1 Hz, up to 100 Hz bursts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At absolute minimum, keep corrections on their own port. On a single-port build you have to multiplex RTCM input and NMEA output on the same UART — and a misconfiguration there looks exactly like a dead correction source, so you will spend an afternoon replacing cables for a config problem.&lt;/p&gt;

&lt;p&gt;Dual-port wiring example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Septentrio receiver                Flight controller (Pixhawk class)
-------------------                ---------------------------------
UART1 TX  ----------------------&amp;gt;  GPS1 RX
UART1 RX  &amp;lt;----------------------  GPS1 TX
GND       -----------------------  GND
5V        -----------------------  5V (dedicated UART 5V, not the servo rail)

Septentrio receiver                NTRIP / telemetry link
-------------------                ----------------------
UART2 RX  &amp;lt;--- RTCM v3 corrections ---  radio or companion TX
UART2 TX  --- NMEA mirror (opt.)  ---&amp;gt;  radio or companion RX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Baud rate: match exactly, and know the headroom
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;115200 bps is the safe default&lt;/strong&gt;, and what the parameter tables below assume (&lt;code&gt;GPS_BAUD_RATE=9&lt;/code&gt; on ArduPilot, &lt;code&gt;GPS_1_BAUD=115200&lt;/code&gt; on PX4).&lt;/li&gt;
&lt;li&gt;AsteRx-m3 Pro+ UARTs on HB10/HB59 support &lt;strong&gt;up to 4 Mbps&lt;/strong&gt;. That headroom exists for raw multi-constellation SBF logging — full-band data will not fit in 115200.&lt;/li&gt;
&lt;li&gt;Change baud on &lt;strong&gt;one side at a time&lt;/strong&gt; and re-verify. Changing both ends blind is how two working ports become one broken link.&lt;/li&gt;
&lt;li&gt;A baud mismatch typically shows as garbage or framing errors rather than a clean "no data" — which is exactly why it gets misdiagnosed as a dead receiver.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Protocol direction — the silent failure
&lt;/h2&gt;

&lt;p&gt;This is the one that catches experienced people:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Carries&lt;/th&gt;
&lt;th&gt;Goes to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NMEA 0183&lt;/td&gt;
&lt;td&gt;receiver -&amp;gt; autopilot&lt;/td&gt;
&lt;td&gt;GGA/RMC position + status&lt;/td&gt;
&lt;td&gt;GPS port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SBF&lt;/td&gt;
&lt;td&gt;receiver -&amp;gt; logger/computer&lt;/td&gt;
&lt;td&gt;PVTGeodetic, AttEuler, observables&lt;/td&gt;
&lt;td&gt;SD card, ROS driver, post-processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTCM v2/v3&lt;/td&gt;
&lt;td&gt;base -&amp;gt; rover (&lt;strong&gt;IN&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;corrections&lt;/td&gt;
&lt;td&gt;rover's correction port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMR v2.0 / CMR+&lt;/td&gt;
&lt;td&gt;base -&amp;gt; rover (&lt;strong&gt;IN&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;corrections&lt;/td&gt;
&lt;td&gt;rover's correction port&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;NMEA and SBF are outputs you enable. RTCM/CMR is an &lt;strong&gt;input the rover must be told to accept&lt;/strong&gt;. A receiver happily streaming perfect NMEA while ignoring its RTCM input will sit in Single forever with a healthy-looking data link — no error, no warning, nothing in a log to point at.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A script that names the fault
&lt;/h2&gt;

&lt;p&gt;Guessing is what costs the afternoon, so here is a small checker. It reads the port for a few seconds and classifies what it sees: no bytes at all, bytes but nothing valid, binary SBF only, or NMEA with fix type, satellite count and &lt;strong&gt;age of differential corrections&lt;/strong&gt; parsed straight out of GGA (fields 6, 7 and 13).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prove what a GNSS receiver is actually sending on a serial port.

Usage:
    python gnss_stream_check.py /dev/ttyUSB0 115200
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_nmea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pure parser: summarise an NMEA byte stream (no serial port needed).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gga&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rmc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sbf_syncs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\xfa\xfa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GGA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gga&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;        &lt;span class="c1"&gt;# 0 none, 1 GPS, 2 DGPS, 4 RTK fixed, 5 RTK float
&lt;/span&gt;                    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# age of differential corrections (s)
&lt;/span&gt;                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rmc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;  &lt;span class="c1"&gt;# pyserial, only needed for live capture
&lt;/span&gt;    &lt;span class="n"&gt;ser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;


&lt;span class="n"&gt;FIX_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GPS (single)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DGPS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RTK fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RTK float&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; lines=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GGA=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gga&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; RMC=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rmc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SBF_syncs=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sbf_syncs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; No bytes at all: TX/RX swapped, wrong port, baud mismatch, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;or receiver output disabled on this port.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sbf_syncs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; Bytes but no valid NMEA or SBF: almost always a baud mismatch.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gga&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sbf_syncs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; Binary SBF only on this port. Fine for logging, but the autopilot &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;needs NMEA on its port.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; fix=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FIX_NAMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) sats=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sats&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;correction_age=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   Never RTK: corrections are not reaching the receiver &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(check RTCM is wired IN, not OUT).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;   Corrections are arriving but stale (&amp;gt;10 s): link/caster problem, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not receiver.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;baud&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;115200&lt;/span&gt;
    &lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_nmea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The classifier has four outcomes. Exercised against synthetic NMEA and SBF streams, they print like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# healthy stream: GGA quality 4 = RTK fixed, fresh corrections
bytes=149 lines=2 GGA=1 RMC=1 SBF_syncs=0
-&amp;gt; fix=4 (RTK fixed) sats=18 correction_age=1.2s

# bytes arriving, but nothing valid -&amp;gt; baud mismatch
bytes=200 lines=0 GGA=0 RMC=0 SBF_syncs=0
-&amp;gt; Bytes but no valid NMEA or SBF: almost always a baud mismatch.

# receiver configured for raw logging only
bytes=56 lines=0 GGA=0 RMC=0 SBF_syncs=2
-&amp;gt; Binary SBF only on this port. Fine for logging, but the autopilot needs NMEA on its port.

# NMEA fine, but corrections never arrive
-&amp;gt; fix=1 (GPS (single)) sats=9 correction_age=None
   Never RTK: corrections are not reaching the receiver (check RTCM is wired IN, not OUT).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last branch is the important one. NMEA GGA fix quality is &lt;code&gt;4&lt;/code&gt; = RTK fixed, &lt;code&gt;5&lt;/code&gt; = RTK float, &lt;code&gt;1&lt;/code&gt; = single, &lt;code&gt;0&lt;/code&gt; = no fix. And a &lt;code&gt;correction_age&lt;/code&gt; above ~10 s means corrections &lt;em&gt;are&lt;/em&gt; arriving but are stale — a link or caster problem, not a receiver problem. Two numbers, and the whole "is it the radio or the receiver" argument is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Autopilot parameters
&lt;/h2&gt;

&lt;p&gt;ArduPilot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_TYPE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Septentrio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_BAUD_RATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;115200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_RATE_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;10 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_AUTO_CONFIG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Receiver configured on every boot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GPS2_TYPE&lt;/code&gt; / &lt;code&gt;GPS2_BAUD_RATE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;9 / 9&lt;/td&gt;
&lt;td&gt;Second receiver or dual-antenna heading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PX4:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_GNSS_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Septentrio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_CONFIG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TELEM2&lt;/td&gt;
&lt;td&gt;Serial port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_BAUD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;115200&lt;/td&gt;
&lt;td&gt;Must match receiver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPS_1_PROTOCOL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Septentrio SBF/NMEA (autodetect usually works)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One driver per port. Do not point two autopilot drivers — or an autopilot plus a companion logging script — at the same UART. They fight over the stream and both look intermittent.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Fault isolation table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No data at all&lt;/td&gt;
&lt;td&gt;TX/RX swapped, wrong port, baud mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbage / framing errors&lt;/td&gt;
&lt;td&gt;Baud or protocol mismatch on that port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D fix but never RTK Fixed&lt;/td&gt;
&lt;td&gt;Corrections not arriving, or RTCM directed OUT instead of IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed -&amp;gt; float, recovers when you move&lt;/td&gt;
&lt;td&gt;Multipath (canopy, buildings, vehicle body) — antenna placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix lost near power lines, fences, 4G sites&lt;/td&gt;
&lt;td&gt;In-band RF interference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data dies at high output rate&lt;/td&gt;
&lt;td&gt;Baud too low for the stream set — reduce blocks or raise baud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two receivers conflict / intermittent position&lt;/td&gt;
&lt;td&gt;Two drivers on one UART, or mismatched &lt;code&gt;GPS2_*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resets to defaults after power cycle&lt;/td&gt;
&lt;td&gt;Configuration not stored as boot config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  8. Why a perfectly configured port can still lose the fix
&lt;/h2&gt;

&lt;p&gt;A port can be flawless and the fix still drops, because the receiver's front end is being desensitised. Power lines, electric fences, 4G/5G sites and deliberate jamming all put energy in band. AIM+ on our receivers rejects roughly &lt;strong&gt;40-60 dB&lt;/strong&gt; of in-band interference versus ~25 dB typical for consumer GNSS modules, and it runs continuously with no configuration.&lt;/p&gt;

&lt;p&gt;The tell is the shape of the failure: if C/N0 sags &lt;strong&gt;across many satellites at once&lt;/strong&gt; and the drop correlates with &lt;em&gt;position&lt;/em&gt; rather than sky view, your link is fine and the RF environment is not. If only low-elevation satellites fade and it recovers as soon as you clear the trees, that is multipath — an antenna placement problem, not a receiver one.&lt;/p&gt;




&lt;p&gt;The full port/baud/protocol guide plus an SBF log parser and a synthetic SBF generator (handy for testing a pipeline without driving to a field) live in the repo: &lt;a href="https://github.com/uavgnss6-bot/septentrio-gnss-integration-guide" rel="noopener noreferrer"&gt;septentrio-gnss-integration-guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;More on the hardware: &lt;a href="https://uav-gnss.com/product-category/gnss-receiver/" rel="noopener noreferrer"&gt;GNSS receivers&lt;/a&gt; | &lt;a href="https://uav-gnss.com/aim-resilient-gnss/" rel="noopener noreferrer"&gt;AIM+ resilient GNSS&lt;/a&gt; | &lt;a href="https://uav-gnss.com/blog/" rel="noopener noreferrer"&gt;integration guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you allocate your ports?&lt;/strong&gt; Do you keep corrections on a dedicated UART, or multiplex RTCM and NMEA on one link — and does your correction age stay steady through a whole pass?&lt;/p&gt;

</description>
      <category>gnss</category>
      <category>robotics</category>
      <category>embedded</category>
      <category>uav</category>
    </item>
    <item>
      <title>Parsing Septentrio SBF logs in Python: fix-quality analysis and RTK drop forensics</title>
      <dc:creator>UAV GNSS</dc:creator>
      <pubDate>Fri, 18 Sep 2026 02:33:11 +0000</pubDate>
      <link>https://dev.to/uavgnss6bot/parsing-septentrio-sbf-logs-in-python-fix-quality-analysis-and-rtk-drop-forensics-3f3g</link>
      <guid>https://dev.to/uavgnss6bot/parsing-septentrio-sbf-logs-in-python-fix-quality-analysis-and-rtk-drop-forensics-3f3g</guid>
      <description>&lt;p&gt;If you build robots that depend on RTK, you eventually hit the moment where the fix drops from &lt;strong&gt;RTK Fixed to RTK Float&lt;/strong&gt; and nothing on your ground station explains why. The autopilot log says "float". The correction source looks healthy. The sky is clear.&lt;/p&gt;

&lt;p&gt;The answer is almost always in the receiver's raw log. If that receiver is a Septentrio, the raw log is &lt;strong&gt;SBF (Septentrio Binary Format)&lt;/strong&gt; — and it is very parseable once you know where the traps are.&lt;/p&gt;

&lt;p&gt;This walks through parsing SBF in Python and turning it into a fix-quality + drop-event report you can actually reason about. Fair disclosure up front: we're UAV GNSS, a Septentrio receiver maker, and everything below is what we built into a small open-source parser after our first version quietly produced wrong coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SBF is, in 30 seconds
&lt;/h2&gt;

&lt;p&gt;SBF is a binary block format. Each block is framed by a &lt;code&gt;0xFA 0xFA&lt;/code&gt; sync marker, followed by a header (block ID, block length, TOW, GPS week) and a payload, and closed by a CRC-16/X25 checksum. Two record types do most of the work for RTK forensics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PVTGeodetic&lt;/strong&gt; — position, plus the fix mode and the satellite count (&lt;code&gt;NrSV&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AttEuler&lt;/strong&gt; — roll / pitch / heading and their accuracies (relevant if you run dual-antenna heading)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix mode is a small integer, and it is the field you care about most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MODE_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No GNSS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Single&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Differential&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RTK Float&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RTK Fixed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only extract one number from a receiver log all day, make it this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three bugs that bite everyone writing an SBF parser
&lt;/h2&gt;

&lt;p&gt;We rewrote ours after v1 emitted plausible-but-wrong latitude/longitude — the worst possible failure mode, because nothing crashes and your data looks fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Field offsets are not where you guess they are.&lt;/strong&gt; In the PVTGeodetic payload we parse lat/lon/height at byte offsets &lt;strong&gt;8 / 16 / 24&lt;/strong&gt;, with &lt;code&gt;mode&lt;/code&gt; at offset &lt;strong&gt;6&lt;/strong&gt; and &lt;code&gt;NrSV&lt;/code&gt; at &lt;strong&gt;62&lt;/strong&gt;. Pick wrong offsets and you still get numbers in a believable numeric range. Always sanity-check your first epoch against a known surveyed point or the receiver's own web interface readout before you trust a single row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Resync at the byte level — never skip a fixed stride.&lt;/strong&gt; If the sync marker doesn't match, scan forward byte by byte for the next &lt;code&gt;0xFA 0xFA&lt;/code&gt; instead of assuming an 8-byte block header and jumping. A dropped or partially-written block then self-heals instead of desynchronising the entire rest of the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Validate the CRC.&lt;/strong&gt; SBF uses &lt;strong&gt;CRC-16/X25&lt;/strong&gt; (poly &lt;code&gt;0x1021&lt;/code&gt; reflected, init &lt;code&gt;0xFFFF&lt;/code&gt;, xorout &lt;code&gt;0xFFFF&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;crc16_x25&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0x8408&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With CRC checking on, corrupt blocks are counted and dropped rather than parsed into your CSV. In our own tamper test, flipping one byte in a 30-record log dropped the parsed record count from 30 to 29 — exactly the behaviour you want from a forensics tool.&lt;/p&gt;

&lt;p&gt;One more that is easy to forget: timestamps. SBF gives you GPS week + time-of-week in milliseconds. Converting that to UTC means applying the leap-second offset (&lt;code&gt;UTC = GPS - 18 s&lt;/code&gt;, valid through 2026) — otherwise your drop events are timestamped 18 seconds off and won't line up with your flight-controller log.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;The parser lives in our &lt;a href="https://github.com/uavgnss6-bot/septentrio-gnss-integration-guide" rel="noopener noreferrer"&gt;integration guide repo&lt;/a&gt;, alongside a generator that produces synthetic SBF logs with valid CRCs so you can test without flying anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# dump a CSV: tow, wn, mode, nrsv, lat, lon, height[, roll, pitch, heading]&lt;/span&gt;
python3 sbf-parser.py log.sbf &lt;span class="nt"&gt;--check-crc&lt;/span&gt; &lt;span class="nt"&gt;--utc&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; out.csv

&lt;span class="c"&gt;# or skip the CSV and get the drop report straight to the console&lt;/span&gt;
python3 sbf-parser.py log.sbf &lt;span class="nt"&gt;--check-crc&lt;/span&gt; &lt;span class="nt"&gt;--utc&lt;/span&gt; &lt;span class="nt"&gt;--analyze&lt;/span&gt;

&lt;span class="c"&gt;# synthetic test log with two embedded 3-second RTK float drops&lt;/span&gt;
python3 make-sample-sbf.py &lt;span class="nt"&gt;--drops&lt;/span&gt; 10,20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--analyze&lt;/code&gt; prints a fix-quality summary followed by every drop event, defined as a contiguous run of non-RTK-Fixed epochs following a fixed one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fix-quality summary (30 epochs):
  RTK Fixed     27  ( 90.0%)
  RTK Float      3  ( 10.0%)

Drop events (RTK Fixed -&amp;gt; degraded):
  #1  2026-08-27T01:46:15Z: RTK Fixed -&amp;gt; RTK Float for 3.0 s
      (NrSV at onset 12 -&amp;gt; min 6)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That mini-report is the whole point: &lt;strong&gt;duration&lt;/strong&gt; tells you how severe the event was, &lt;strong&gt;onset satellite count vs minimum during the drop&lt;/strong&gt; tells you whether satellites disappeared, and an unrecovered run gets flagged as still degraded at the end of the log instead of being silently truncated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a drop report like a diagnostician
&lt;/h2&gt;

&lt;p&gt;Three completely different faults produce the same "it went to float" symptom on your GCS. The log separates them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the log shows&lt;/th&gt;
&lt;th&gt;Most likely cause&lt;/th&gt;
&lt;th&gt;What to change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NrSV&lt;/code&gt; collapses across many constellations, degrades in one specific location, recovers when you move away&lt;/td&gt;
&lt;td&gt;RF interference desensitising the front end (power lines, electric fences, nearby 4G/5G sites)&lt;/td&gt;
&lt;td&gt;Antenna placement first; then receiver front-end capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NrSV&lt;/code&gt; stays high, fix degrades only under canopy or beside structures, recovers as soon as you clear them&lt;/td&gt;
&lt;td&gt;Multipath — delayed reflections corrupting carrier-phase&lt;/td&gt;
&lt;td&gt;Raise the antenna, add a proper ground plane, move it off motor/ESC wiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NrSV&lt;/code&gt; is healthy but the fix degrades for a stretch and returns&lt;/td&gt;
&lt;td&gt;Corrections were lost or stale (NTRIP dropout, radio link, cellular handover)&lt;/td&gt;
&lt;td&gt;Log correction age; check the link, not the receiver&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice you correlate three things on the same timeline: fix mode, satellite count, and correction age. Fix mode alone tells you &lt;em&gt;that&lt;/em&gt; you lost it; the other two tell you &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for receiver choice
&lt;/h2&gt;

&lt;p&gt;The third row in that table is a link problem, and no receiver on earth fixes it. But the first row is a hardware differentiator. Receivers differ enormously in how much in-band interference they tolerate before the front end is desensitised: consumer-grade modules typically manage on the order of &lt;strong&gt;25 dB&lt;/strong&gt; of suppression, while our AIM+ receivers are specified at &lt;strong&gt;40–60 dB&lt;/strong&gt;. Same sky, same corrections, same wiring — different outcome when you drive past a pylon or work under a transmission line.&lt;/p&gt;

&lt;p&gt;You cannot see that difference in a datasheet comparison table. You &lt;em&gt;can&lt;/em&gt; see it in an SBF drop report, which is why we instrument it.&lt;/p&gt;

&lt;p&gt;If you want the written-up field guide for the satellite-count-collapse pattern, we keep one here: &lt;a href="https://uav-gnss.com/satellite-count-drop-rtk-fixed-sbf-analyzer-guide/" rel="noopener noreferrer"&gt;Satellite count drop and RTK fix loss — SBF analyzer guide&lt;/a&gt;, and the receiver range that logs this data is &lt;a href="https://uav-gnss.com/product-category/gnss-receiver/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;Do you capture raw SBF/UBX on your vehicles, or only the autopilot's fix-status field? And when you analyse a drop, what do you plot first — satellite count, C/N0, or correction age? Curious what patterns other people are chasing.&lt;/p&gt;

</description>
      <category>gnss</category>
      <category>python</category>
      <category>robotics</category>
      <category>uav</category>
    </item>
  </channel>
</rss>
