@cleoqc - please read and comment.
The problem is that the easygps library is broken as it is missing its fundamental “read” method.
Viz.:
This is the equivelent Python code generated by Bloxter when it attempts to read the GPS sensor.
import easygopigo3 as easy
import time
import easygps
gpg = easy.EasyGoPiGo3()
gps_sensor = easygps.GPS()
gps_sensor.read() # take an initial reading
# start
while True:
print(gps_sensor.get_latitude())
time.sleep(0.2)
time.sleep (1)
print(gps_sensor.get_longitude())
time.sleep(0.2)
time.sleep (1)
time.sleep(0.05) # slowdown
This is the easygpg.py library file:
(Note the absence of a “read” method.)
import serial, time, sys
import re
from multiprocessing import Process, Queue
en_debug = False
def debug(in_str):
if en_debug:
print(in_str)
patterns=["\$GPGGA",
"[0-9]+\.[0-9]+", # timestamp hhmmss.ss
"[0-9]+.[0-9]+", # latitude of position
"[NS]", # North or South
"[0-9]+.[0-9]+", # longitude of position
"[EW]", # East or West
"[012]", # GPS Quality Indicator
"[0-9]+", # Number of satellites
"[0-9]+.[0-9]+", # horizontal dilution of precision x.x
"[0-9]+\.[0-9]+" # altitude x.x
]
class GPS():
def __init__(self,port='/dev/ttyAMA0',baud=9600,timeout=0, delay_to_die=10):
self.ser = serial.Serial(port, baud, timeout=timeout)
self.ser.flush()
self.raw_line = ""
self.gga = []
self.validation =[] # contains compiled regex
self.time_to_die = time.time()
self.delay_to_die = delay_to_die # how long do we keep a value before requesting another read
self.threaded_access = None
self.queue = None
# compile regex once to use later
for i in range(len(patterns)-1):
self.validation.append(re.compile(patterns[i]))
self.clean_data()
def clean_data(self):
'''
clean_data:
ensures that all relevant GPS data is set to either empty string
or -1.0, or -1, depending on appropriate type
This occurs right after initialisation or
after 50 attemps to reach GPS
'''
self.timestamp = ""
self.lat = -1.0 # degrees minutes and decimals of minute
self.NS = ""
self.lon = -1.0
self.EW = ""
self.quality = -1
self.satellites = -1
self.altitude = -1.0
self.latitude = -1.0 #degrees and decimals
self.longitude = -1.0
self.fancylat = "" #
def _threaded_read(self, q):
'''
Start a thread to query the GPS as this can loop up to 50 times when the GPS is unable
to get a proper reading. As soon as a reading is valid, exit background thread.
An exit code of 0 means we have a valid reading.
An exit code of 1 means we cannot get a reading after 50 reads, or an empty line
'''
debug ("starting thread")
for i in range(50):
time.sleep(0.5)
self.raw_line = self.ser.readline()
# an empty line means the GPS isnt ready to send data
if (self.raw_line == b""):
exit(1)
try:
self.line = self.raw_line.decode('utf-8')
self.line = self.line.strip()
except: #leftover from Python2
self.line = self.raw_line
debug(f"DATA #{i}: {self.line}")
if self.validate(self.line):
q.put (self.line)
self.ser.reset_input_buffer()
exit(0)
# else:
# print ("invalid line")
debug ("Giving up")
exit (1)
def control_panel_read(self):
'''
Starts a non-blocking background thread that attempts 50 times at most to get valid data from GPS
If a thread is found to be already running, then just exit
It's expected that control_panel_read() is being called repeatitively
and that a reading will caught on the following call.
If valid data is not found, then clean up data in GPS instance
'''
return_value = 0
if self.queue == None:
self.queue = Queue()
if self.threaded_access == None:
# start the background process
debug ("starting first process")
self.threaded_access = Process(target=self._threaded_read, args=(self.queue,))
self.threaded_access.start()
if self.threaded_access.is_alive() is False:
# query background process
if self.threaded_access.exitcode == 0:
if self.queue.empty() is False:
gga = self.queue.get()
self.parse_gga(gga)
debug ("FOUND: ")
debug (self.gga)
self.time_to_die = time.time()
return 0
else:
# we got an exit code indicating failure
debug("cleaning up")
self.clean_data()
return_value = 1
# start the next background process
debug("starting next process")
self.threaded_access = Process(target=self._threaded_read, args=(self.queue,))
self.threaded_access.start()
else:
# debug("nothing to do but wait")
return_value = 2
return return_value
def validate(self, in_line):
'''
Runs regex validation on a GPGAA sentence.
Returns False if the sentence is mangled
Return True if everything is all right and sets internal
class members.
'''
if in_line == "":
return False
if in_line[:6] != "$GPGGA":
return False
self.gga = in_line.split(",")
# debug("in validate():")
# debug (self.gga)
#Sometimes multiple GPS data packets come into the stream. Take the data only after the last '$GPGGA' is seen
try:
ind=self.gga.index('$GPGGA',5,len(self.gga))
self.gga=self.gga[ind:]
except ValueError:
pass
if len(self.gga) != 15:
debug ("Failed: wrong number of parameters ")
debug (self.gga)
return False
for i in range(len(self.validation)-1):
if len(self.gga[i]) == 0:
debug (f"Failed: empty string {i}")
self.ser.flush()
self.ser.reset_input_buffer()
return False
test = self.validation[i].match(self.gga[i])
if test == None:
debug (f"Failed: wrong format on parameter {i}: {self.gga[i]}")
return False
else:
# debug("Passed %d"%i)
pass
self.parse_gga(self.gga)
return True
def parse_gga(self, in_gga):
# in_gga may be a str or a list. We need it in a list
try:
self.gga = in_gga.split(",")
except:
pass
try:
self.timestamp = self.gga[1]
self.lat = float(self.gga[2])
self.NS = self.gga[3]
self.lon = float(self.gga[4])
self.EW = self.gga[5]
self.quality = int(self.gga[6])
self.satellites = int(self.gga[7])
self.altitude = float(self.gga[9])
self.latitude = self.lat // 100 + self.lat % 100 / 60
if self.NS == "S":
self.latitude = - self.latitude
self.longitude = self.lon // 100 + self.lon % 100 / 60
if self.EW == "W":
self.longitude = -self.longitude
except ValueError as e:
debug( "FAILED: invalid value")
debug( e)
def get_latitude(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return round(self.latitude,6)
def get_longitude(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return round(self.longitude,6)
def convert(self, coord_in):
degrees = int(coord_in)
# print(f"Degrees: {degrees}")
minutes = int((coord_in - degrees) * 60 )
# print(f"Minutes: {minutes}")
seconds = round((coord_in - degrees - (minutes/60)) * 3600,2)
# print(f"Seconds: {seconds}")
return f"{degrees}°{minutes}'{seconds:{2}.2f}"
def get_latitude_str(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return self.convert(abs(self.latitude))+self.NS
def get_longitude_str(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return self.convert(abs(self.longitude))+self.EW
def get_utc0_time(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return f"{self.timestamp[:2]}:{self.timestamp[2:4]}:{self.timestamp[4:6]}"
def get_nb_satellites(self):
if time.time() - self.time_to_die > self.delay_to_die:
self.read()
return self.satellites
if __name__ =="__main__":
print("STARTING GPS")
gps = GPS()
while True:
time.sleep(5)
in_data = gps.read()
# print (f"read: {in_data}")
print(f"lat: {gps.lat}, N/S: {gps.NS}, long: {gps.lon}, E/W: {gps.EW}, nb satellites: {gps.satellites}, google: {gps.convert(abs(gps.latitude))+gps.EW}, {gps.convert(abs(gps.longitude))+gps.NS} ")
assert(gps.convert(45.504103)=="45°30'14.77")
assert(gps.convert(73.806683)=="73°48'24.06")
This is the grovegps library file gps_reading.py
Note that this works, (but doesn’t parse properly), and has a “read” method.
#!/usr/bin/env python
# This example demonstrates how to use the Grove GPS sensor with the GoPiGo3.
# Connect the GPS sensor to the SERIAL port on the GoPiGo3. This port
# is on the right hand side of the GoPiGo3.
from __future__ import print_function
import serial, time, sys
import re
en_debug = False
def debug(in_str):
if en_debug:
print(in_str)
patterns=["$GPGGA",
"/[0-9]{6}\.[0-9]{2}/", # timestamp hhmmss.ss
"/[0-9]{4}.[0-9]{2,/}", # latitude of position
"/[NS]", # North or South
"/[0-9]{4}.[0-9]{2}", # longitude of position
"/[EW]", # East or West
"/[012]", # GPS Quality Indicator
"/[0-9]+", # Number of satellites
"/./", # horizontal dilution of precision x.x
"/[0-9]+\.[0-9]*/" # altitude x.x
]
class GROVEGPS():
def __init__(self,port='/dev/serial0',baud=9600,timeout=0):
self.ser = serial.Serial(port,baud,timeout=timeout)
self.ser.flush()
self.raw_line = ""
self.gga = []
self.validation =[] # contains compiled regex
# compile regex once to use later
for i in range(len(patterns)-1):
self.validation.append(re.compile(patterns[i]))
self.clean_data()
# self.get_date() # attempt to gete date from GPS.
def clean_data(self):
'''
clean_data:
ensures that all relevant GPS data is set to either empty string
or -1.0, or -1, depending on appropriate type
This occurs right after initialisation or
after 50 attemps to reach GPS
'''
self.timestamp = ""
self.lat = -1.0 # degrees minutes and decimals of minute
self.NS = ""
self.lon = -1.0
self.EW = ""
self.quality = -1
self.satellites = -1
self.altitude = -1.0
self.latitude = -1.0 #degrees and decimals
self.longitude = -1.0
self.fancylat = "" #
def get_date(self):
'''
attempt to get date from GPS data. So far no luck. GPS does
not seem to send date sentence at all
function is unfinished
'''
valid = False
for i in range(50):
time.sleep(0.5)
print (i)
self.raw_line = self.ser.readline().strip()
if self.raw_line[:6] == "GPZDA": # found date line!
print ("")
print (self.raw_line)
def read(self):
'''
Attempts 50 times at most to get valid data from GPS
Returns as soon as valid data is found
If valid data is not found, then clean up data in GPS instance
'''
valid = False
for i in range(50):
time.sleep(0.5)
self.raw_line = self.ser.readline().strip()
print(self.raw_line)
if self.validate(self.raw_line):
valid = True
break;
if valid:
return self.gga
else:
print("Invalid/no data received")
self.clean_data()
return []
def validate(self,in_line):
'''
Runs regex validation on a GPGAA sentence.
Returns False if the sentence is mangled
Return True if everything is all right and sets internal
class members.
'''
if in_line == "":
return False
if in_line[:6] != "$GPGGA":
return False
self.gga = in_line.split(",")
debug (self.gga)
#Sometimes multiple GPS data packets come into the stream. Take the data only after the last '$GPGGA' is seen
try:
ind=self.gga.index('$GPGGA',5,len(self.gga))
self.gga=self.gga[ind:]
except ValueError:
pass
if len(self.gga) != 15:
debug ("Failed: wrong number of parameters ")
debug (self.gga)
return False
for i in range(len(self.validation)-1):
if len(self.gga[i]) == 0:
debug ("Failed: empty string %d"%i)
return False
test = self.validation[i].match(self.gga[i])
if test == False:
debug ("Failed: wrong format on parameter %d"%i)
return False
else:
debug("Passed %d"%i)
try:
self.timestamp = self.gga[1]
self.lat = float(self.gga[2])
self.NS = self.gga[3]
self.lon = float(self.gga[4])
self.EW = self.gga[5]
self.quality = int(self.gga[6])
self.satellites = int(self.gga[7])
self.altitude = float(self.gga[9])
self.latitude = self.lat // 100 + self.lat % 100 / 60
if self.NS == "S":
self.latitude = - self.latitude
self.longitude = self.lon // 100 + self.lon % 100 / 60
if self.EW == "W":
self.longitude = -self.longitude
except ValueError:
debug( "FAILED: invalid value")
return True
if __name__ =="__main__":
gps = GROVEGPS()
print("Reading GPS sensor for location . . . ")
print("If you are not seeing coordinates appear, your sensor needs to be")
print("outside to detect GPS satellites.")
while True:
print("Reading GPS sensor for location . . . ")
time.sleep(2)
in_data = gps.read()
if in_data != []:
# Print out the GPS coordinates in Google Coodinates.
# You should be able to paste these into maps.google.com
# and see your location.
print ("Lat: {} North/South: {} Long: {} East/West: {}".format(gps.lat, gps.NS, gps.lon,gps.EW))
print ("Expanded Lat: {} Expanded Long: {}".format(gps.latitude, gps.longitude))
What I suspect happened is that, originally, Bloxter was intended to run as a part of DexterOS and be compatible with the GrovePi - and that you would use GrovePi methods on an earlier GoPiGo.
The easygps library appears to be a half-finished attempt to port the Grove library over to the GoPiGo. I am going to try renaming methods in the Grove version to conform to what the GoPiGo wants and see what happens.
Additional notes:
There are also easypicamera and easyusb libraries in the egg file for the gps. IMHO, I think the easygps should be part of easysensors.