Ok, this code grabs GPS coordinates from the Telit modem and runs on a raspberry Pi with raspbian. This code handles a lot of error cases and is fairly robust when the modem gets confused.
You need to install cu
with:
sudo apt-get install cu
You need to install pexpect with:
pip install pexpect
… and you really ought to install pexpect in a virtual environment.
#!/usr/bin/env python3
"""exerciser for gps functions of telit/sixfab wireless card
Usage:
python3 gpstest.py [--verbose] [--count n] [--hdop v] --device /dev/ttyUSB2
--device sets the device to send AT commands to and is passed to cu, assuming 8-bit and 115200 bps
--hdop v max horizontal dilution of precision acceptable (default 99, 1.5 or so is good)
--verbose enables lots of debugging output
--count n performs n GPS fixes (default 1)
"""
import argparse
import time
import os
import pexpect
import datetime
verbose = False
child: pexpect.pty_spawn.spawn # so pycharm does not complain
# TODO: command line argument to set how many times you try to get a decent fix (now hardcoded to 30)
# TODO: best-effort (min hdop value) as well as threshold
# TODO: check_device() needs to do a better job
# TODO: full-on class interface
class GPSError(Exception):
pass
class GPSDataError(GPSError):
pass
class GPSBusted(GPSError):
pass
class GPSTimeout(GPSError):
pass
def wait_for_result():
"""wait for OK, ERROR, or timeout return "OK" or raise an exception"""
i = child.expect([pexpect.TIMEOUT, "OK\r\n", "ERROR\r\n"], timeout=10)
if i == 0:
if verbose:
print(f"[gps] Timed out!")
raise GPSTimeout
elif i == 1:
if verbose:
print(f"[gps] Saw OK")
return "OK"
elif i == 2:
if verbose:
print(f"[gps] Saw ERROR")
raise GPSError
raise GPSBusted
def send_at_ok():
"""simple test to send an at command and make sure things are cool"""
child.send("\r")
time.sleep(0.1)
for k in range(10):
if verbose:
print(f"[gps] Trying to send 'AT', try #{k+1}")
child.send("AT\r")
try:
_ = wait_for_result()
except GPSTimeout:
time.sleep(0.5)
else:
return
raise GPSTimeout
def get_gpsp_status():
"""get the current state of the gps (in case it is on)"""
if verbose:
print(f"[gps] Getting GPS power state with AT$GPSP?")
child.send(f"AT$GPSP?\r")
i = child.expect([pexpect.TIMEOUT, r"[$]GPSP:[ ]+([0-9])\r\n"])
rc = None
if i == 0:
if verbose:
print(f"[gps] Timeout, failing!")
raise GPSTimeout
elif i == 1:
if verbose:
print(f"[gps] results: {child.match[1]}")
rc = child.match[1]
rc = int(rc.decode('utf-8'))
if verbose:
print(f"[gps] GPS power state is {rc}")
_ = wait_for_result()
return rc
def send_gpsp(state):
"""turn the gps on or off"""
child.send(f"AT$GPSP={state}\r")
_ = wait_for_result()
return True
def send_gpsp_on():
"""turn on gps"""
if verbose:
print(f"[gps] Sending AT$GPSP=1 to power on GPS")
return send_gpsp(1)
def send_gpsp_off():
"""turn off gps"""
if verbose:
print(f"[gps] Sending AT$GPSP=0 to power off GPS")
return send_gpsp(0)
def get_one_position():
"""get position, matched string or None"""
if verbose:
print(f"[gps] Sending AT$GPSACP to get current position")
child.send("AT$GPSACP\r")
i = child.expect([pexpect.TIMEOUT, r"[$]GPSACP:[ ]+([0-9A-Z,.]+)\r\n"])
rc = None
if i == 0:
if verbose:
print(f"[gps] Timeout, failing")
raise GPSTimeout
elif i == 1:
if verbose:
print(f"[gps] results: {child.match[1]}")
rc = child.match[1]
rc = rc.decode('utf-8')
_ = wait_for_result()
return rc
def parse_latitude(lat):
"""produce normal signed fractional latitude from weird gps value"""
direction = lat[-1:]
sign = 0
if direction == "N":
sign = 1
elif direction == "S":
sign = -1
return sign * (float(int(lat[:2])) + float(lat[2:-1]) / 60)
def parse_longitude(lon):
"""produce normal signed fractional longitude from weird gps value"""
direction = lon[-1:]
sign = 0
if direction == "E":
sign = 1
elif direction == "W":
sign = -1
return sign * (float(int(lon[:3])) + float(lon[3:-1]) / 60)
def parse_date_time(date, timeofday):
"""not used and probably not useful as far as I can see"""
day = int(date[0:2])
mon = int(date[2:4])
yy = int(date[4:6])
hh = int(timeofday[0:2])
mm = int(timeofday[2:4])
ss = int(timeofday[4:6])
us = int(timeofday[7:]) * 1000
return datetime.datetime(yy, mon, day, hh, mm, ss, us, datetime.timezone(datetime.timedelta(0)))
def parse_values(position):
"""break out values from GPS sentence"""
if len(position) != 12:
raise GPSDataError
rc = dict(
GMTIME=position[0],
LATITUDE=position[1],
LONGITUDE=position[2],
HDOP=position[3],
ALTITUDE=position[4],
fix=position[5],
COG=position[6],
SPKM=position[7],
SPKN=position[8],
DATE=position[9],
NSAT_GPS=position[10],
NSAT_GLONASS=position[11],
)
rc["latitude"] = parse_latitude(rc["LATITUDE"])
rc["longitude"] = parse_longitude(rc["LONGITUDE"])
rc["altitude"] = float(rc["ALTITUDE"])
rc["hdop"] = float(rc["HDOP"])
rc["whence"] = parse_date_time(rc["DATE"], rc["GMTIME"])
return rc
def get_position(hdop, total=30):
"""
get gps position.
retry until a valid sentence is returned with an hdop less or equal to than the passed hdop
returns a dict
"""
for k in range(total):
if k > 0:
time.sleep(10)
results = get_one_position()
if results is None:
time.sleep(0.1)
continue
position = results.split(',')
if len(position) >= 6 and position[5] == '3' and hdop >= float(position[3]):
return parse_values(position)
if verbose:
print(f"[gps] get_position(): Try #{k+1}, got {position[5]}, hdop was '{position[3]}'")
if verbose:
print(f"[gps] Got nothing")
return None
def check_device(d):
return os.path.exists(d)
def main():
global child, verbose
ap = argparse.ArgumentParser()
ap.add_argument("--verbose", action='store_true', default=False)
ap.add_argument("--device", required=True, type=str)
ap.add_argument("--hdop", required=False, type=float, default=99.0)
ap.add_argument("--count", required=False, type=int, default=1)
args = ap.parse_args()
verbose = args.verbose
if not check_device(args.device):
print(f"[gps.error] {args.device} does not exist")
exit(1)
child = pexpect.spawn(f"cu --nostop --parity none --baud 115200 --line {args.device} dir")
time.sleep(0.5)
try:
send_at_ok()
if get_gpsp_status() == 0:
_ = send_gpsp_on()
for k in range(args.count):
if k > 0:
time.sleep(15)
x = get_position(args.hdop)
if x is not None:
if verbose:
print(f"[gps] ***RESULTS***: {x['latitude']:.3f},{x['longitude']:.3f}")
else:
print(f"{x['latitude']:.3f},{x['longitude']:.3f}")
else:
print(f"[gps] nowhere")
time.sleep(0.1)
_ = send_gpsp_off()
except GPSError as e:
print(f"[gps] ***ERROR***: {e}:")
print(child)
child.sendline("~.")
time.sleep(0.5)
if __name__ == "__main__":
main()