10constexpr size_t GSV_PAGE_COUNT_FIELD = 1;
11constexpr size_t GSV_PAGE_NUMBER_FIELD = 2;
12constexpr size_t GSV_SATELLITE_COUNT_FIELD = 3;
13constexpr size_t GSV_FIRST_SATELLITE_FIELD = 4;
14constexpr size_t GSV_SATELLITE_FIELDS = 4;
15constexpr int GSV_SATELLITES_PER_PAGE = 4;
16constexpr int MAX_GSV_PAGES = 64;
17constexpr int MAX_GSV_SATELLITES = MAX_GSV_PAGES * GSV_SATELLITES_PER_PAGE;
18constexpr int MAX_GSV_SIGNAL_ID = 15;
19constexpr int MAX_GSV_SATELLITE_ID = 999;
27 if (input.
type() ==
"GGA") {
34 return !status->valid ||
NMEA::rmc(input).has_value();
43 namespace Id = SatelliteIds;
44 constexpr int GPS_SYSTEM_ID = 1;
45 constexpr int GLONASS_SYSTEM_ID = 2;
46 constexpr int GALILEO_SYSTEM_ID = 3;
47 constexpr int BEIDOU_SYSTEM_ID = 4;
48 constexpr int QZSS_SYSTEM_ID = 5;
49 constexpr int NAVIC_SYSTEM_ID = 6;
50 constexpr int SBAS_LAST_PRN = 158;
51 constexpr int QZSS_LEGACY_LAST_NMEA_ID = 200;
52 constexpr int BEIDOU_UNAMBIGUOUS_FIRST_NMEA_ID = Id::QZSS_LAST_NMEA_ID + 1;
54 if ((talker ==
"GP" || (talker ==
"GN" && (!systemId || *systemId == GPS_SYSTEM_ID))) &&
satelliteId &&
57 return Constellation::SBAS;
60 return Constellation::GPS;
63 return Constellation::GLONASS;
66 return Constellation::Galileo;
68 if (talker ==
"GB" || talker ==
"BD") {
69 return Constellation::BeiDou;
71 if (talker ==
"GQ" || talker ==
"PQ" || talker ==
"QZ") {
72 return Constellation::QZSS;
75 return Constellation::NavIC;
78 return Constellation::Unknown;
83 return Constellation::GPS;
84 case GLONASS_SYSTEM_ID:
85 return Constellation::GLONASS;
86 case GALILEO_SYSTEM_ID:
87 return Constellation::Galileo;
88 case BEIDOU_SYSTEM_ID:
89 return Constellation::BeiDou;
91 return Constellation::QZSS;
93 return Constellation::NavIC;
95 return Constellation::Unknown;
99 return Constellation::Unknown;
103 if (
id >= Id::FIRST_LOCAL_ID &&
id < Id::SBAS_FIRST_NMEA_ID) {
104 return Constellation::GPS;
106 if (
id >= Id::GLONASS_FIRST_NMEA_ID &&
id <= Id::GLONASS_LAST_NMEA_ID) {
107 return Constellation::GLONASS;
109 if (
id >= Id::QZSS_FIRST_NMEA_ID &&
id <= QZSS_LEGACY_LAST_NMEA_ID) {
110 return Constellation::QZSS;
114 if ((
id >= BEIDOU_UNAMBIGUOUS_FIRST_NMEA_ID &&
id <= Id::BEIDOU_LEGACY_LAST_NMEA_ID) ||
115 (
id >= Id::BEIDOU_EXTENDED_FIRST_NMEA_ID &&
id <= Id::BEIDOU_EXTENDED_LAST_NMEA_ID)) {
116 return Constellation::BeiDou;
118 if (
id >= Id::GALILEO_FIRST_NMEA_ID &&
id <= Id::GALILEO_LAST_NMEA_ID) {
119 return Constellation::Galileo;
121 return Constellation::Unknown;
126 if (input.
type() !=
"GSV" || input.
count < GSV_FIRST_SATELLITE_FIELD) {
129 const auto& f = input.
fields;
130 const auto total = number<int>(f[GSV_PAGE_COUNT_FIELD]);
131 const auto message = number<int>(f[GSV_PAGE_NUMBER_FIELD]);
132 const auto count = number<int>(f[GSV_SATELLITE_COUNT_FIELD]);
135 *total > MAX_GSV_PAGES || *message < 1 || *message > *total || *count < 0 || *count > MAX_GSV_SATELLITES ||
136 *total != std::max(1, (*count + GSV_SATELLITES_PER_PAGE - 1) / GSV_SATELLITES_PER_PAGE)) {
139 const int entries = std::min(GSV_SATELLITES_PER_PAGE, *count - (*message - 1) * GSV_SATELLITES_PER_PAGE);
140 const size_t end = GSV_FIRST_SATELLITE_FIELD + entries * GSV_SATELLITE_FIELDS;
141 const size_t fields = input.
count - GSV_FIRST_SATELLITE_FIELD;
142 const size_t satelliteSlots = fields / GSV_SATELLITE_FIELDS;
143 const size_t trailingFields = fields % GSV_SATELLITE_FIELDS;
144 if (satelliteSlots <
static_cast<size_t>(entries) || satelliteSlots > GSV_SATELLITES_PER_PAGE ||
145 trailingFields > 1) {
148 const size_t signalIndex = GSV_FIRST_SATELLITE_FIELD + satelliteSlots * GSV_SATELLITE_FIELDS;
150 for (
size_t index = end; index < signalIndex; ++index) {
151 if (!f[index].empty()) {
155 GSV result{system, *total, *message, *count};
156 if (trailingFields == 1 && !f[signalIndex].empty()) {
157 const auto signal = number<int>(f[signalIndex],
HEX_BASE);
158 if (!signal || *signal < 0 || *signal > MAX_GSV_SIGNAL_ID) {
161 result.signal = *signal;
163 for (
size_t index = GSV_FIRST_SATELLITE_FIELD; index < end; index += GSV_SATELLITE_FIELDS) {
164 const auto id = number<int>(f[index]);
165 if (!
id || *id < 1 || *id > MAX_GSV_SATELLITE_ID) {
174 result.satellites.push_back(satellite);
184 _batchStartedUs.reset();
191 const auto complete = [&]() {
192 auto epoch =
flush();
193 update.completed.insert(update.completed.end(), std::make_move_iterator(epoch.begin()),
194 std::make_move_iterator(epoch.end()));
196 if (validNavigation(input)) {
198 if (time && time != _time) {
204 if (input.
type() ==
"GSV") {
205 const auto parsed =
gsv(input);
209 const auto& page = *parsed;
210 auto& signalReports = _views[page.constellation];
211 if (page.message == 1 && signalReports[page.signal].complete()) {
214 auto& report = _views[page.constellation][page.signal];
215 if (page.message == 1) {
216 report = {page.messages, page.satelliteCount, 1, receivedAtUs, {}};
218 if (report.messages != page.messages || report.count != page.satelliteCount || report.next != page.message) {
219 _views[page.constellation].erase(page.signal);
222 report.satellites.insert(report.satellites.end(), page.satellites.begin(), page.satellites.end());
223 report.timestamp = std::min(report.timestamp, receivedAtUs);
225 update.accepted =
true;
227 const auto& f = input.
fields;
232 std::optional<int> system;
239 std::map<GPSConstellation, Used> reports;
242 reports[explicitSystem].timestamp = receivedAtUs;
249 if (f[index].empty()) {
252 const auto id = number<int>(f[index]);
253 if (!
id || *
id <= 0) {
260 auto& report = reports[constellation];
261 report.timestamp = receivedAtUs;
263 report.ids.insert(
satelliteId(constellation, *
id));
266 if (reports.empty()) {
269 if (!_views.empty() && std::any_of(reports.begin(), reports.end(),
270 [
this](
const auto& report) { return _used.contains(report.first); })) {
273 for (
auto& [constellation, report] : reports) {
274 _used[constellation] = std::move(report);
276 update.accepted =
true;
278 if (update.accepted) {
279 if (!_batchStartedUs) {
280 _batchStartedUs = receivedAtUs;
282 _lastAcceptedUs = receivedAtUs;
289 if (!_batchStartedUs) {
292 return std::min(*_batchStartedUs +
static_cast<uint64_t
>(
BATCH_TIMEOUT.count()),
293 _lastAcceptedUs +
static_cast<uint64_t
>(
IDLE_TIMEOUT.count()));
304 _batchStartedUs.reset();
305 std::map<GPSConstellation, SatelliteSystem> systems;
306 std::map<GPSConstellation, std::set<int>> satellites;
307 const auto includeView = [&systems](
GPSConstellation constellation, uint64_t timestamp) {
308 auto& out = systems[constellation];
309 out.constellation = constellation;
310 out.inViewTimestampUs = out.inViewTimestampUs ? std::min(out.inViewTimestampUs, timestamp) : timestamp;
312 for (
const auto& [system, signalReports] : _views) {
313 for (
const auto& [signal, report] : signalReports) {
314 if (!report.complete()) {
319 includeView(system, report.timestamp);
324 for (
const auto& satellite : report.satellites) {
325 includeView(satellite.constellation, report.timestamp);
326 satellites[satellite.constellation].insert(satellite.id);
330 for (
auto& [system, view] : satellites) {
331 systems[system].inView =
static_cast<int>(view.size());
333 for (
const auto& [system, used] : _used) {
334 auto& out = systems[system];
335 out.constellation = system;
336 out.inUseTimestampUs = used.timestamp;
337 out.inUse =
static_cast<int>(used.ids.size());
340 for (
auto& [system, report] : systems) {
341 result.push_back(std::move(report));
std::optional< uint64_t > deadlineUs() const
Update ingest(const Sentence &input, uint64_t receivedAtUs)
static constexpr std::chrono::microseconds BATCH_TIMEOUT
SatelliteEpoch flushDue(uint64_t nowUs)
static constexpr std::chrono::microseconds IDLE_TIMEOUT
constexpr size_t GSA_DIMENSION
constexpr size_t GSA_SATELLITE_SLOTS
constexpr size_t RMC_MIN_FIELDS
constexpr size_t RMC_DATE
constexpr size_t GSA_MIN_FIELDS
constexpr size_t GSA_SYSTEM_ID
constexpr size_t GSA_FIRST_SATELLITE
constexpr size_t GGA_MIN_FIELDS
constexpr size_t UTC_TIME
constexpr unsigned THREE_D
constexpr unsigned NO_FIX
std::optional< GSV > gsv(const Sentence &input)
GPSConstellation satelliteConstellation(std::string_view talker, std::optional< int > systemId, std::optional< int > satelliteId)
std::optional< UtcDate > rmcDate(std::string_view field)
std::optional< int > utcMilliseconds(std::string_view field)
std::vector< SatelliteSystem > SatelliteEpoch
std::optional< RMC > rmc(const Sentence &input)
int satelliteId(GPSConstellation constellation, int wireId)
Canonical constellation-local identifier; preserve unknown/vendor ranges unchanged.
std::optional< GGA > gga(const Sentence &input)
Quality zero is a valid fix-loss observation with empty coordinates and optional hemisphere fields.
std::optional< NavigationStatus > navigationStatus(const Sentence &input)
GPSConstellation constellation
std::string_view type() const
std::array< std::string_view, MAX_FIELDS > fields
std::string_view talker() const