4

I have the following python script:

#! /usr/bin/python

import os
from gps import *
from time import *
import time
import threading
import sys

gpsd = None #seting the global variable

class GpsPoller(threading.Thread):
   def __init__(self):
      threading.Thread.__init__(self)
      global gpsd #bring it in scope
      gpsd = gps(mode=WATCH_ENABLE) #starting the stream of info
      self.current_value = None
      self.running = True #setting the thread running to true

   def run(self):
      global gpsd
      while gpsp.running:
         gpsd.next() #this will continue to loop and grab EACH set of gpsd info to clear the buffer

if __name__ == '__main__':
   gpsp = GpsPoller() # create the thread
   try:
      gpsp.start() # start it up
      while True:

         print gpsd.fix.speed

         time.sleep(1) ## <<<< THIS LINE HERE

   except (KeyboardInterrupt, SystemExit): #when you press ctrl+c
      print "\nKilling Thread..."
      gpsp.running = False
      gpsp.join() # wait for the thread to finish what it's doing
   print "Done.\nExiting."

I'm not very good with python, unfortunately. The script should be multi-threaded somehow (but that probably doesn't matter in the scope of this question).

What baffles me is the gpsd.next() line. If I get it right, it was supposed to tell the script that new gps data have been acquired and are ready to be read.

However, I read the data using the infinite while True loop with a 1 second pause with time.sleep(1).

What this does, however, is that it sometimes echoes the same data twice (the sensor hasn't updated the data in the last second). I figure it also skips some sensor data somehow too.

Can I somehow change the script to print the current speed not every second, but every time the sensor reports new data? According to the data sheet it should be every second (a 1 Hz sensor), but obviously it isn't exactly 1 second, but varies by milliseconds.

martineau
  • 119,623
  • 25
  • 170
  • 301
Frantisek
  • 7,485
  • 15
  • 59
  • 102

2 Answers2

2

As a generic design rule, you should have one thread for each input channel or more generic, for each "loop over a blocking call". Blocking means that the execution stops at that call until data arrives. E.g. gpsd.next() is such a call.

To synchronize multiple input channels, use a Queue and one extra thread. Each input thread should put its "events" on the (same) queue. The extra thread loops over queue.get() and reacts appropriately.

From this point of view, your script need not be multithreaded, since there is only one input channel, namely the gpsd.next() loop.

Example code:

from gps import *

class GpsPoller(object):
   def __init__(self, action):
      self.gpsd = gps(mode=WATCH_ENABLE) #starting the stream of info
      self.action=action

   def run(self):
      while True:
         self.gpsd.next()
         self.action(self.gpsd)

def myaction(gpsd):
    print gpsd.fix.speed

if __name__ == '__main__':
   gpsp = GpsPoller(myaction)
   gpsp.run() # runs until killed by Ctrl-C

Note how the use of the action callback separates the plumbing from the data evaluation.

To embed the poller into a script doing other stuff (i.e. handling other threads as well), use the queue approach. Example code, building on the GpsPoller class:

from threading import Thread
from Queue import Queue

class GpsThread(object):
    def __init__(self, valuefunc, queue):
        self.valuefunc = valuefunc
        self.queue = queue
        self.poller = GpsPoller(self.on_value)

    def start(self):
        self.t = Thread(target=self.poller.run)
        self.t.daemon = True  # kill thread when main thread exits
        self.t.start()

    def on_value(self, gpsd):
        # note that we extract the value right here.
        # Otherwise it could change while the event is in the queue.
        self.queue.put(('gps', self.valuefunc(gpsd)))


def main():
    q = Queue()
    gt = GpsThread(
            valuefunc=lambda gpsd: gpsd.fix.speed,
            queue = q
            )
    print 'press Ctrl-C to stop.'
    gt.start()
    while True:
        # blocks while q is empty.
        source, data = q.get()
        if source == 'gps':
            print data

The "action" we give to the GpsPoller says "calculate a value by valuefunc and put it in the queue". The mainloop sits there until a value pops out, then prints it and continues.

It is also straightforward to put other Thread's events on the queue and add the appropriate handling code.

Torben Klein
  • 2,943
  • 1
  • 19
  • 24
1

I see two options here:

  1. GpsPoller will check if data changed and raise a flag
  2. GpsPoller will check id data changed and put new data in the queue.

Option #1:

global is_speed_changed = False

def run(self):
      global gpsd, is_speed_changed
      while gpsp.running:
         prev_speed = gpsd.fix.speed
         gpsd.next()
         if prev_speed != gpsd.fix.speed
            is_speed_changed = True  # raising flag

while True:
        if is_speed_changed:
           print gpsd.fix.speed
           is_speed_changed = False

Option #2 ( I prefer this one since it protects us from raise conditions):

gpsd_queue = Queue.Queue()

def run(self):
      global gpsd
      while gpsp.running:
         prev_speed = gpsd.fix.speed
         gpsd.next()
         curr_speed = gpsd.fix.speed
         if prev_speed != curr_speed:
            gpsd_queue.put(curr_speed) # putting new speed to queue

while True:
    # get will block if queue is empty 
    print gpsd_queue.get()
Samuel
  • 3,631
  • 5
  • 37
  • 71
  • I wouldn't want to rely on speed. There is other data gpsd reports. Even if the speed is the same (i'm travelling at the constant rate), I still want the new data to be reported. – Frantisek Aug 07 '16 at 05:54
  • I'm not sure this has so much to do with "speed" as it does "location". The location polling is more important, because whatever is being measured could be going the same constant speed, although changing coordinates which this would fail to account for... – l'L'l Aug 07 '16 at 05:55
  • Even if the location is the same, I still think it should be reported, i.e. the script should tell me, "hey, I just got a response from the satellites that your location really is still the same." – Frantisek Aug 07 '16 at 05:56
  • @RichardRodriguez: One thing to keep in mind also is that according to the documentation it recommends polling more often than every 2 seconds to avoid a backlog in your socket buffers. ( see the warning here: http://catb.org/gpsd/client-howto.html ); so your thinking is correct there. – l'L'l Aug 07 '16 at 05:58
  • @RichardRodriguez Whatever, you can use one of the options above to compare locations and print hey, I just got a response from the satellites that your location really is still the same." message. BTW why not just increase sleep time to bee 100% sure that data is updated? – Samuel Aug 07 '16 at 06:03
  • @Samuel I didn't mean that literally :) I was just pointing out that I was hoping that gpsd "knows" that it has received a new set of data (unrelated to whether the data is different or not) and send them out in the script. Is this possible, or not? – Frantisek Aug 07 '16 at 06:08
  • 1
    I wouldn't bother checking if the queue is empty. This will create a busy loop and chew up the CPU. Instead, just call `gpsd_queue.get()` for each iteration. The method will block until new data becomes available. – Dunes Aug 07 '16 at 06:28