Files
roro9stack/lib/gnss/src/nmea_parser.h
T
twislaandClaude Opus 5.5 c9b1ecb772 M2 step 2: NMEA parser (host-tested)
lib/gnss: RMC, GGA, GSA and GSV into one GnssState: Fix type, position,
altitude, speed, course, HDOP, UTC time (trusted only with a Fix) and the
satellites in view across constellations. GSV sequences are kept per
constellation and signal band and merged per satellite with the stronger
SNR; GSA's system ID marks which satellites are used. 16 tests, using
lines captured from the device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-04 22:05:01 +02:00

79 lines
2.7 KiB
C++

#pragma once
#include <cstddef>
#include <cstdint>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
namespace roro::gnss {
enum class Constellation : uint8_t { Unknown, Gps, Glonass, Galileo, BeiDou, Qzss, Navic };
const char* constellationName(Constellation c);
enum class FixType : uint8_t { None, TwoD, ThreeD };
struct Satellite {
Constellation system = Constellation::Unknown;
int prn = 0;
int elevation = 0; // degrees above the horizon
int azimuth = 0; // degrees from north, clockwise
int snr = -1; // dB-Hz; -1 when not tracked
bool used = false; // part of the current Fix
};
// Everything the receiver has said, as of the last sentence (see CONTEXT.md: Fix).
struct GnssState {
FixType fix = FixType::None;
bool positionValid = false;
double latitude = 0, longitude = 0; // degrees, south and west negative
bool altitudeValid = false;
float altitudeM = 0; // above mean sea level
float speedKmh = 0;
bool courseValid = false;
float courseDeg = 0;
int satellitesUsed = 0;
float hdop = 99.9f;
// UTC, trusted only with a Fix: the receiver reports a time without one, from its own clock.
bool timeValid = false;
int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
std::vector<Satellite> satellites; // in view, all constellations, one entry per satellite
int64_t utcSeconds() const; // seconds since 1970-01-01 UTC; meaningful when timeValid
};
// NMEA 0183 (4.10 style, as the Cap's AT6668 sends it): RMC, GGA, GSA and GSV, with the
// constellation taken from the talker ID or GSA's system ID field. Host-tested; no hardware here.
class NmeaParser {
public:
// Raw bytes from the UART, in any chunks.
void feed(const char* data, size_t len);
// One sentence without its line end. False if malformed or its checksum is wrong.
bool sentence(const std::string& line);
const GnssState& state() const { return state_; }
uint32_t goodSentences() const { return good_; }
uint32_t badSentences() const { return bad_; }
private:
using Fields = std::vector<std::string>;
void rmc(const Fields& f);
void gga(const Fields& f);
void gsa(const Fields& f, Constellation talker);
void gsv(const Fields& f, Constellation talker);
void rebuildSatellites();
static constexpr size_t kMaxLine = 120;
std::string line_;
bool overflow_ = false;
uint32_t good_ = 0, bad_ = 0;
GnssState state_;
// GSV sequences per (constellation, signal): pending until their last message.
std::map<std::pair<int, int>, std::vector<Satellite>> pending_, inView_;
std::map<int, std::set<int>> used_; // per constellation, from GSA
};
} // namespace roro::gnss