Public Access
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
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user