Search code examples
arduinoarduino-c++

How do I return several optional variables from a function in C/C++?


I'm programming an ESP32 with a SIM7000 4G modem and the modem library has a function that gets a CSV string with 21 variables from the modem and partially parses it into variables. The original author only cared about a small number of the variables and so passed pointers.

If I wanted to modify the code to make all of the variables available, it seems unwieldy to pass 21 pointers, especially if I may only care about a few of the variables at a time. Is this a case for a struct? What is the most user-friendly way to make the data available?

The function sends an AT command to a cellular modem with GPS chip to (SIM7000). On receiving the command, the chip parses the NEMA data from GPS internally and returns and CSV string with all the relevant data.

//FUNCTION FROM LIBRARY
 // get GPS information
  bool getGPS(float *lat, float *lon, float *speed=0, float *alt=0, float *course=0, int *vsat=0, int *usat=0) {
    //String buffer = "";
    bool fix = false;

    sendAT(GF("+CGNSINF"));
    if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) {
      return false;
    }

    stream.readStringUntil(','); // mode [GNSS off=0, GNSS on=1]
    if ( stream.readStringUntil(',').toInt() == 1 ) fix = true;
    stream.readStringUntil(','); //utctime yyyyMMddhhmmss.sss
    *lat =  stream.readStringUntil(',').toFloat(); //lat ±dd.dddddd
    *lon =  stream.readStringUntil(',').toFloat(); //lon ±ddd.dddddd
    if (alt != NULL) *alt =  stream.readStringUntil(',').toFloat(); //altitude metere
    if (speed != NULL) *speed = stream.readStringUntil(',').toFloat(); //speed Km/hour [0,999.99]
    if (course != NULL) *course = stream.readStringUntil(',').toFloat(); //course over ground degrees [0,360.00]
    stream.readStringUntil(',');//Fix mode [0,1,2]
    stream.readStringUntil(',');//Reserved1
    stream.readStringUntil(',');//HDOP [0,99.9]
    stream.readStringUntil(',');//PDOP [0,99.9]
    stream.readStringUntil(',');//VDOP [0,99.9]
    stream.readStringUntil(',');//Reserved2
    if (vsat != NULL) *vsat = stream.readStringUntil(',').toInt(); //viewed satelites
    if (usat != NULL) *usat = stream.readStringUntil(',').toInt(); //used satelites
    stream.readStringUntil(',');//GLONASS Sats used
    stream.readStringUntil(',');//Reserved3
    stream.readStringUntil(',');//C/N0 max (dBHz 0-55)
    stream.readStringUntil(',');//HPA (meters [0,9999.9])
    stream.readStringUntil(',');//VPA (meters [0,9999.9])
    stream.readStringUntil('\n');

    waitResponse();

    return fix;
}

//CALL FROM CODE
  gps_fixstatus = modem.getGPS(&gps_latitude, &gps_longitude, &gps_speed, &gps_altitude, &gps_course, &gps_view_satellites, &gps_used_satellites);
  if ( gps_fixstatus ) {
    gps_altitude=gps_altitude*3.2808; 
    sprintf(payload, "{\"lat\":%8f,\"long\":%8f,\"speed\":%2f,\"head\":%2f,\"alt\":%2f}", gps_latitude,gps_longitude, gps_speed, gps_course, gps_altitude); //JSON-ify
    SerialMon.println(payload); //JSON object
 }

Full code: Library-see line 771 / Code calling library-line 134

I forked the library to have it return GPS course info too and thought why not make all the returned data available but it doesn't seem user-friendly to have to pass 21 pointers or define a 21 member struct and pass that if their main code only cares about lat/lon even just speed.

One library that I like and use with a standalone gps receiver was TinyGPS++. It returns the data as part of an object, e.g. Serial.print(TinyGPSplus.location.lat()). Is that the best approach? I've been learning C/C++ via Arduino by mashing together different examples.


Solution

  • Yes. This is a good case for a struct (c/c++ keyword for Structure(s)).

    "Structures—sometimes referred to as aggregates—are collections of related variables under one name. Structures may contain variables of many different data types—in contrast to arrays that contain only elements of the same data type." - C How to program 6.Ed by Paul and Harvey Deitel.

    In your case you have to return about 21 different variables right? so using a struct to create a custom data type lets say gpsData with its members being the 21 variables you want to return will give you the ability to write a function that returns data using "gpsData" in a compact way. Of course you will have to assign each derived/parsed data item from the gsm module to each member of "gpsData".

    Referring to your code:

    bool getGPS(float *lat, float *lon, float *speed=0, float *alt=0, float *course=0, int *vsat=0, int *usat=0) {
    //String buffer = "";
    bool fix = false;
    
    sendAT(GF("+CGNSINF"));
    if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) {
      return false;
    }
    
    stream.readStringUntil(','); // mode [GNSS off=0, GNSS on=1]
    if ( stream.readStringUntil(',').toInt() == 1 ) fix = true;
    stream.readStringUntil(','); //utctime yyyyMMddhhmmss.sss
    *lat =  stream.readStringUntil(',').toFloat(); //lat ±dd.dddddd
    *lon =  stream.readStringUntil(',').toFloat(); //lon ±ddd.dddddd
    if (alt != NULL) *alt =  stream.readStringUntil(',').toFloat(); //altitude metere
    if (speed != NULL) *speed = stream.readStringUntil(',').toFloat(); //speed Km/hour [0,999.99]
    if (course != NULL) *course = stream.readStringUntil(',').toFloat(); //course over ground degrees [0,360.00]
    stream.readStringUntil(',');//Fix mode [0,1,2]
    stream.readStringUntil(',');//Reserved1
    stream.readStringUntil(',');//HDOP [0,99.9]
    stream.readStringUntil(',');//PDOP [0,99.9]
    stream.readStringUntil(',');//VDOP [0,99.9]
    stream.readStringUntil(',');//Reserved2
    if (vsat != NULL) *vsat = stream.readStringUntil(',').toInt(); //viewed satelites
    if (usat != NULL) *usat = stream.readStringUntil(',').toInt(); //used satelites
    stream.readStringUntil(',');//GLONASS Sats used
    stream.readStringUntil(',');//Reserved3
    stream.readStringUntil(',');//C/N0 max (dBHz 0-55)
    stream.readStringUntil(',');//HPA (meters [0,9999.9])
    stream.readStringUntil(',');//VPA (meters [0,9999.9])
    stream.readStringUntil('\n');
    
    waitResponse();
    
    return fix;
    
    }
    

    These variables: float *lat, float *lon, float *speed=0, float *alt=0, float *course=0, int *vsat=0, int *usat=0 can be put in a struct like this:

    struct gpsData{
        bool fix;
        float lat;
        float lon;
        float speed;
        float alt;
        float course;
        int vsat;
        int usat;
        ....x_datatype y_variable;
    };
    

    Where x_datatype y_variable refers to other member variables you may want to add.

    Your getGPS function will now become something like this:

    gpsData getGPS(){
         gsmData temp;
         temp.fix = false;
        // required processing...
        // ...
    
        return temp;
    }
    

    When getGPS() is called, as in the following example:

    gpsData parsed_gps_data = getGPS();
    

    You can now get the parameters/members/attributes you are interested in like this:

    parsed_gps_data.fix;
    parsed_gps_data.lat;
    parsed_gps_data.lon;
    

    I hope this explanation makes everything clear.