7

I wanted to use libgps to interface with gpsd daemon. That's why I've implemented a little testing application in order to extract a value from a specific satellite.

The documentation on its HOWTO page tells us that

The tricky part is interpreting what you get from the blocking read. The reason it’s tricky is that you’re not guaranteed that every read will pick up exactly one complete JSON object from the daemon. It may grab one response object, or more than one, or part of one, or one or more followed by a fragment.

As recommended the documentation, the PACKET_SET mask bit is checked before doing anything else.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <gps.h>
#include <pthread.h>

pthread_t t_thread;

struct t_args {
   unsigned int ID;
};

unsigned int status = 0;
int elevation;

int p_nmea(void *targs);

void start_test(void)
{
    struct t_args *args = malloc(sizeof *args);
    status = 1;
    args->ID = 10;

    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    if (pthread_create(&t_thread, &attr, (void *)&p_nmea, args) != 0)
    {
        perror("create: \n");
    }
}

int test_result(int * Svalue)
{
    int res;

    if(status == 1)
    {
        void * t_res;
        if(pthread_tryjoin_np(t_thread, &t_res) != 0)
        {
            status = 1;
        }
        else
        {       
            if((int)t_res == 1)
            {
                res = 3;
                *Svalue = elevation;
                elevation = 0;
            }
            else
            {
                res = 4;            
            }
        }
    }
    return res;
}

int p_nmea(void *targs)
{
    struct t_args *thread_args = targs;     
    struct gps_data_t gpsdata;
    int ret = -1;
    int count = 10;
    int i,j;

   if(gps_open((char *)"localhost", (char *)DEFAULT_GPSD_PORT, &gpsdata) != 0)
   {
        (void)fprintf(stderr, "cgps: no gpsd running or network error: %d, %s\n", errno, gps_errstr(errno));
        return (-1);
   }
   else
   {
        (void)gps_stream(&gpsdata, WATCH_ENABLE, NULL);
        do 
        {
            if(!gps_waiting(&gpsdata, 1000000))
            {       
                (void)gps_close(&gpsdata);
            }
            else
            {
                if(gps_read(&gpsdata) == -1)
                {
                    return (-1);
                }
                else
                {
                    if(gpsdata.set & PACKET_SET)
                    {
                       for (i = 0; i < MAXCHANNELS; i++)
                       {
                            for (j = 0; j < gpsdata->satellites_visible; j++)
                            {
                                if(gpsdata->PRN[i] == thread_args.ID) 
                                {
                                    elevation = (int)gpsdata->elevation[i];
                                    ret = 1;
                                    break;
                                }       
                            }
                            if(gpsdata->PRN[i] == thread_args.ID)
                            {
                                break;
                            }
                       }
                    }
                }
            }
            --count;
        }while(count != 0);
    }
    (void)gps_stream(&gpsdata, WATCH_DISABLE, NULL);
    (void)gps_close(&gpsdata);
    (void)free(thread_args);
    (void)pthread_exit((void*) ret);
}

As recommended in the documentation too, I had a look at cgps and gpxlogger for example codes, but the subtleties of libgps escape me. A while loop has been added before gps_waiting() in order to get, at least, one entire response object. Before introducing pthread, I noted that call the function test_result() just after start_test() take few seconds before returning an answer. By using a thread I thought that 3 would be imediately returned, then 3 or 4 .. but it's not ! I am still losing few seconds. In addition, I voluntarily use pthread_tryjoin_np() because its man page says

The pthread_tryjoin_np() function performs a nonblocking join with the thread

Can anybody give me his help, I guess that I understand something wrongly but I am not able to say about which part yet? Basically, why I come into the do while loop at least four times before returning the first value ?

EDIT 1 :

After reading the documentation HOWTO again I highlight the lines :

The fact that the data-waiting check and the read both block means that, if your application has to deal with other input sources than the GPS, you will probably have to isolate the read loop in a thread with a mutex lock on the gps_data structure.

I am a little bit confusing. What does it really mean ?

ogs
  • 1,139
  • 8
  • 19
  • 42
  • I'm not familiar with reading data from GPS, but your threading as posted looks like trouble. In code elsewhere are you calling `start_test` then `test_result` on the next line? And what exactly are you trying to do? Read elevation data from GPS satellite 10? I started on an answer but it turned out I had too many questions. Your EDIT1 quoted documentation simply means that calls to `gps_waiting()` and `gps_read()` are going to block. If you only have a single thread in your process, this means your entire process will come to a screeching halt until the blocking function call returns. (cont) – yano Mar 04 '16 at 06:28
  • (cont) So if your process is waiting on other input sources, you will lose any data coming in while your single thread is blocking on `gps_waiting()` and/or `gps_read()`. This is why it suggests making these calls a separate thread whose sole job is simply to block on these calls and retrieve data from them. Meanwhile, other threads in your process are free for anything else your process may want to do. The mutex for the `gps_data` structure is recommended to protect access to it in case other threads are modifying and/or reading from it. Mutexes ensure data concurrency and integrity in (cont) – yano Mar 04 '16 at 06:36
  • (cont) multi-threaded environments. If all of this is new to you, then I recommend reading a pthread tutorial. This is a good one: https://computing.llnl.gov/tutorials/pthreads/ . But based on what you're trying to do here, you may not even need threads. If this is just a test/proof-of-concept that you can in fact read GPS data, I wouldn't mess with threads. Threads always add complication and open the door for strange bugs if not used correctly. Sorry for the long comment; wish SO had a page for answers and a page for discussion. – yano Mar 04 '16 at 06:40
  • @yano thank for these information! I am calling `start_test` and `test_result` functions in the main of another file (containing only `main()`), which includes the file defined in the question. Basically, I would like to implement a process which is allowing to the user to call `start_test()` and getting the result when he wants to by calling `test_result()`. It could be immediately or few minutes after the first starting request. That why, I would like to return 1 to the user if the test is currently not entirely finished and 3 or 4 otherwise. – ogs Mar 04 '16 at 11:44
  • @yano As you identified, I should be blocked until the blocking function call returns and I will unfortunately loss other input data given by the users... That why I firtly directed my implement to thread usage – ogs Mar 04 '16 at 11:46
  • Thanks for the extra info. Yes, if you have other inputs/other things going on in your program, you were correct to use threads. I can post an answer with my 2 cents on how I would implement this with pthreads (maybe not before the bounty deadline, I am at work :( ), but that tutorial I posted should give you good idea how to do it. I can't speak to the JSON stuff. Is there any indication how big a JSON object is? How many bytes you read back when `gps_read()` returns? There must be info somewhere that makes it possible to stitch these potentially fragmented JSON objects back together. – yano Mar 04 '16 at 16:41
  • I suspect all this info is contained in the `struct gps_data_t` structure somehow, but I will need to take a look at the definition. – yano Mar 04 '16 at 16:57
  • @yano As you highlighted, all information are stored in the `strct gps_data_t` defined in the gps header file. Thean, JSON stuff is not really important. There is no reliable indication about the size (or number of bytes). I guess that's because NMEA frames might contain different content depending on the signal quality : `http://www.catb.org/gpsd/client-howto.html`. – ogs Mar 07 '16 at 12:10

1 Answers1

0

Your loop is executing multiple times before returning a full packet because you do not have a sleep condition. Therefore each time the daemon registers a packet (even when not a full NMEA message), the gps_waiting() function returns. I'd recommend sleeping at least as long as it takes your GPS to register a full message.

For example, if you expect GPPAT messages, you could reasonably expect to have 12 characters in the message. Thus at 9600 baud, that would take 1/17.5 seconds or about 57 ms. In this case, your code could look like this:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <gps.h>
#include <pthread.h>

pthread_t t_thread;

struct t_args {
   unsigned int ID;
};

unsigned int status = 0;
int elevation;

int p_nmea(void *targs);

void start_test(void)
{
    struct t_args *args = malloc(sizeof *args);
    status = 1;
    args->ID = 10;

    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    if (pthread_create(&t_thread, &attr, (void *)&p_nmea, args) != 0)
    {
        perror("create: \n");
    }
}

int test_result(int * Svalue)
{
    int res;

    if(status == 1)
    {
        void * t_res;
        if(pthread_tryjoin_np(t_thread, &t_res) != 0)
        {
            status = 1;
        }
        else
        {       
            if((int)t_res == 1)
            {
                res = 3;
                *Svalue = elevation;
                elevation = 0;
            }
            else
            {
                res = 4;            
            }
        }
    }
    return res;
}

int p_nmea(void *targs)
{
    struct t_args *thread_args = targs;     
    struct gps_data_t gpsdata;
    int ret = 0;
    int count = 10;
    int i,j;

   if(gps_open((char *)"localhost", (char *)DEFAULT_GPSD_PORT, &gpsdata) != 0)
   {
        (void)fprintf(stderr, "cgps: no gpsd running or network error: %d, %s\n", errno, gps_errstr(errno));
        return (-1);
   }
   else
   {
        (void)gps_stream(&gpsdata, WATCH_ENABLE, NULL);
        do 
        {
            ret = 0; // Set this here to allow breaking correctly
            usleep(50000); // Sleep here to wait for approx 1 msg
            if(!gps_waiting(&gpsdata, 1000000)) break;

            if(gps_read(&gpsdata) == -1) break;

            if(gpsdata.set & PACKET_SET)
            {
              for (i = 0; i < MAXCHANNELS && !ret; i++)
              {
                for (j = 0; j < gpsdata.satellites_visible; j++)
                {
                  if(gpsdata.PRN[i] == thread_args.ID) 
                  {
                     elevation = (int)gpsdata.elevation[i]; // Be sure to not deref structure here
                     ret = 1;
                     break;
                  }       
                }
            }
            --count;
        }while(count != 0);
    }
    (void)gps_stream(&gpsdata, WATCH_DISABLE, NULL);
    (void)gps_close(&gpsdata);
    (void)free(thread_args);
    (void)pthread_exit((void*) ret);
}

Alternatively, you could just set your count higher and wait for the full message.

Seth
  • 2,683
  • 18
  • 18