I wrote an OO driver for Teseo-LIV3 GPS module (as used in shabaz ' GPS / Galileo / BeiDou / GLONASS receiver). It knows how to retrieve info from the GPS, in NMEA format. In this series, I'm designing an OO lib to parse the payloads and return the data as objects.
The third post wraps up the library design. Topics:
- utility class
 - example with output
 

Utility class
nmea
This is a pure utility class, that offers functionality to parse particular nmea data types. It's also the one that holds the different NMEA-wide enumeration definitions. The class doesn't have any data member, and all methods are static. You can call each of them right away, without constructing an object (actually: I blocked possibility of instantiating an object).
example:
// retrieve the talker ID from the current reply token rmc.source = nmea::get_talker_id(std::string_view(word));
class definition:
typedef std::chrono::hh_mm_ss<std::chrono::duration<int, std::ratio<1, 1000>>> time_t;
class nmea {
public:
	enum talker_id {
		gps,     			// If system works in GPS only mode
		glonass, 			// If system works in GLONASS only mode
		galileo, 			// If system works in GALILEO only mode
		beidou,  			// If system works in BEIDOU only mode
		qzss,    			// If system works in QZSS only mode
		multiconstellation 	// If system works in multi-constellation mode
	};
	enum dir {
		n,
		s,
		e,
		w
	};
	enum qual : unsigned int {
		q0 = 0,
		q1 = 1,
		q2 = 2,
		q6 = 6
	};
	nmea() = delete; // prevent  creation of objects of this utility class
	static talker_id get_talker_id(const std::string_view& sv);
	static talker_id get_system_id(const std::string_view& sv);
	static float get_coord(const std::string_view& sv);
	static dir get_dir(const std::string_view& sv);
	static void get_time(const std::string_view& sv, time_t& t);
	static void get_date(const std::string_view& sv, std::chrono::year_month_day& d);
	static bool get_valid(const std::string_view& sv);
	static qual get_qual(const std::string_view& sv);
};
implementation:
nmea::talker_id nmea::get_talker_id(const std::string_view& sv) {
	talker_id id = gps;
	if (sv.starts_with("$GP")) {
		id = gps;
	} else if (sv.starts_with("$GL")) {
		id = glonass;
	} else if (sv.starts_with("$GA")) {
		id = galileo;
	} else if (sv.starts_with("$BD")) {
		id = beidou;
	} else if (sv.starts_with("$QZ")) {
		id = qzss;
	} else if (sv.starts_with("$GN")) {
		id = multiconstellation;
	}
	return id;
}
nmea::talker_id nmea::get_system_id(const std::string_view& sv) {
	talker_id id = gps;
	if (sv.starts_with("1")) {
		id = gps;
	} else if (sv.starts_with("2")) {
		id = glonass;
	} else if (sv.starts_with("3")) {
		id = galileo;
	} else if (sv.starts_with("4")) {
		id = beidou;
	} else if (sv.starts_with("5")) {
		id = qzss;
	}
	return id;
}
float nmea::get_coord(const std::string_view& sv) {
	std::string s(sv);
	float coord = std::stof(s.substr(0, 2), nullptr);
	coord += std::stof(s.substr(2), nullptr) / 60.0;
	return coord;
}
nmea::dir nmea::get_dir(const std::string_view& sv) {
	assert(sv.starts_with('N') ||
			sv.starts_with('S') ||
			sv.starts_with('E') ||
			sv.starts_with('W'));
	dir d = n;
	if (sv.starts_with('N')) {
		d = n;
	} else if (sv.starts_with('S')) {
		d = s;
	} else if (sv.starts_with('E')) {
		d = e;
	} else if (sv.starts_with('W')) {
		d = w;
	}
	return d;
}
void nmea::get_time(const std::string_view& sv, time_t& t) {
	std::string s(sv);
		t = time_t{
			std::chrono::hours(std::stoi(s.substr(0,  2))) +
			std::chrono::minutes(std::stoi(s.substr(2,  2))) +
		    std::chrono::seconds(std::stoi(s.substr(4,  2))) +
			std::chrono::milliseconds(std::stoi(s.substr(7)))
	};
/*	t = std::chrono::hh_mm_ss(10.5h + 98min + 2020s + 0.5s);
	t.set_h(std::stoi(s.substr(0,  2)));
	t.set_m(std::stoi(s.substr(2,  2)));
	t.set_s(std::stoi(s.substr(4,  2)));
	t.set_ms(std::stoi(s.substr(7)));*/
}
void nmea::get_date(const std::string_view& sv, std::chrono::year_month_day& d) {
	std::string s(sv);
	d = std::chrono::year_month_day{
			std::chrono::year(std::stoi(s.substr(4)) + 2000), // TODO Y2.1K warning
			std::chrono::month(std::stoi(s.substr(2,  2))),
			std::chrono::day(std::stoi(s.substr(0,  2)))
	};
}
bool nmea::get_valid(const std::string_view& sv) {
	assert(sv.starts_with('A') ||
			sv.starts_with('V'));
	return sv.starts_with('A');
}
nmea::qual nmea::get_qual(const std::string_view& sv) {
	// the typecast from int to enem<unsigned int> below is confirmed
	// to be safe in this case
	// https://www.modernescpp.com/index.php/strongly-typed-enums/
	return static_cast<nmea::qual>(std::stoi(std::string(sv)));
}
Like most of this library code, it's all reasonably straightforward. Splitting off different parts of the exercise to specialised classes, helps. That's also a reason why I created different libraries for GPS interaction and NMEA data parsing. Although they can (and in a later project will) work together, they solve different aspects of API integration. Trying to integrate parsing logic in the GPS interaction can resolve in logic that's hard to untangle. It also makes it harder to use either the GPS communication part, or the parsing part, in isolation.
Example and Testbed
The library's main file serves 2 purposes: Showcase how the parser can be used, and act as a testbed for the code. I hope it's self-explanatory:
#include <string>
#include <vector>
#include <iostream>
#include "nmea.h"
void test_gll() {
	std::string reply = "$GPGLL,5051.83778,N,00422.55809,S,185427.150,V,N*4F";
    nmea::gll o;
    nmea::gll::from_data(reply, o);
    std::cout <<
    		"GLL " << std::endl <<
    		"source: " << o.source << ". " <<
			"lat: " << o.lat << " lon: " << o.lon << ". " <<
    		o.t << ". " <<
			"valid: " << o.valid  << ". " <<
			std::endl;
    return;
}
void test_gga() {
	std::string reply = "$GPGGA,191237.000,5051.78066,N,00422.57079,E,1,05,3.7,027.26,M,47.3,M,,*65";
    nmea::gga o;
    nmea::gga::from_data(reply, o);
    std::cout <<
    		"GGA " << std::endl <<
    		"source: " << o.source << ". " <<
			"lat: " << o.lat << " lon: " << o.lon << ". " <<
    		o.t << ". " <<
			"qual: " << o.qual << ", " <<
			"sats: " << o.sats << ". " <<
			std::endl;
    return;
}
void test_gsa() {
	std::vector<std::string> replies {
		"$GNGSA,A,3,15,18,,,,,,,,,,,4.7,3.7,2.9*2D",
		"$GNGSA,A,3,73,65,81,,,,,,,,,,4.7,3.7,2.9*2E"
	};
	for(auto r : replies) {
		nmea::gsa o;
	    nmea::gsa::from_data(r, o);
	    std::cout <<
	    		"GSA " << std::endl <<
	    		"source: " << o.source << ". " << std::endl <<
	       		"system: " << o.system_id << ". " << std::endl;
	    for(const auto s : o.sats) {
	    	std::cout << "sat prn: " << s << "." <<
					std::endl;
	    }
	}
    return;
}
void test_gsv() {
	std::vector<std::string> replies {
		"$GPGSV,3,1,11,13,79,310,,14,53,113,,05,51,214,,30,47,067,*72",
		"$GPGSV,3,2,11,15,45,295,24,22,44,145,,20,27,192,,07,16,064,*7A",
		"$GPGSV,3,3,11,18,16,298,25,24,08,249,,08,08,029,18,,,,*40",
		"$GLGSV,2,1,08,72,79,113,,74,77,084,,75,38,202,,65,37,317,28*68",
		"$GLGSV,2,2,08,73,34,040,35,71,28,130,,81,13,333,24,82,08,017,*68"
	};
	for(auto r : replies) {
		nmea::gsv o;
	    nmea::gsv::from_data(r, o);
	    std::cout <<
	    		"GSV " << std::endl <<
	    		"source: " << o.source << ". " << std::endl;
	    for(const auto s : o.sats) {
	    	std::cout << "sat prn: " << s.prn << ", elev: " <<
	    			s.elev << ", azim: " << s.azim << ", snr: " << s.snr << "." <<
					std::endl;
	    }
	}
    return;
}
void test_rmc() {
	std::string reply = "$GPRMC,185427.150,V,5051.83778,N,00422.55809,E,,,240724,,,N*7F";
    nmea::rmc o;
    nmea::rmc::from_data(reply, o);
    std::cout <<
    		"RMC " << std::endl <<
    		"source: " << o.source << ". " <<
			"lat: " << o.lat << " lon: " << o.lon << ". " <<
    		o.t << ". " <<
    		o.d << ". " <<
			"valid: " << o.valid  << ". " <<
			std::endl;
    return;
}
int main() {
	test_gll();
	test_gga();
	test_gsa();
	test_gsv();
	test_rmc();
	return 0;
}
All test string come from this investigative post.
Output:
GLL source: 0. lat: 50.864 lon: 7.04263. 18:54:27.150. valid: 0. GGA source: 0. lat: 50.863 lon: 7.04285. 19:12:37.000. qual: 1, sats: 5. GSA source: 5. system: 3. sat prn: 15.sat prn: 18.sat prn: 0....sat prn: 0.GSA source: 5. system: 3. sat prn: 73.sat prn: 65.sat prn: 81.sat prn: 0....sat prn: 0.GSV source: 0. sat prn: 13, elev: 79, azim: 310, snr: 0.sat prn: 14, elev: 53, azim: 113, snr: 0.sat prn: 5, elev: 51, azim: 214, snr: 0.sat prn: 30, elev: 47, azim: 67, snr: 0.GSV source: 0. sat prn: 15, elev: 45, azim: 295, snr: 24.sat prn: 22, elev: 44, azim: 145, snr: 0.sat prn: 20, elev: 27, azim: 192, snr: 0.sat prn: 7, elev: 16, azim: 64, snr: 0.GSV source: 0. sat prn: 18, elev: 16, azim: 298, snr: 25.sat prn: 24, elev: 8, azim: 249, snr: 0.sat prn: 8, elev: 8, azim: 29, snr: 18.sat prn: 0, elev: 0, azim: 0, snr: 0.GSV source: 1. sat prn: 72, elev: 79, azim: 113, snr: 0.sat prn: 74, elev: 77, azim: 84, snr: 0.sat prn: 75, elev: 38, azim: 202, snr: 0.sat prn: 65, elev: 37, azim: 317, snr: 28.GSV source: 1. sat prn: 73, elev: 34, azim: 40, snr: 35.sat prn: 71, elev: 28, azim: 130, snr: 0.sat prn: 81, elev: 13, azim: 333, snr: 24.sat prn: 82, elev: 8, azim: 17, snr: 0.RMC source: 0. lat: 50.864 lon: 7.04263. 18:54:27.150. 2024-07-24. valid: 0.
In an embedded design, you'd use a similar approach. Get data from the GPS in NMEA format. Then use this parser's object to get hold of individual attributes.
Next post: C++ parser library for NMEA GPS data - pt. 4: We have C++ objects and containers: So what?
visit the github repository
Link to all posts.