Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add I2C support and various example fixes #36

Merged
merged 15 commits into from Nov 16, 2019
107 changes: 95 additions & 12 deletions adafruit_gps.py
Expand Up @@ -43,15 +43,17 @@

"""
import time
from micropython import const

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GPS.git"


_GPSI2C_DEFAULT_ADDRESS = const(0x10)

# Internal helper parsing functions.
# These handle input that might be none or null and return none instead of
# throwing errors.


def _parse_degrees(nmea_data):
# Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
# Where ddd is the degrees, mm.mmmm is the minutes.
Expand Down Expand Up @@ -135,11 +137,11 @@ def update(self):
data_type, args = sentence
data_type = bytes(data_type.upper(), "ascii")
# return sentence
if data_type == b'GPGLL': # GLL, Geographic Position – Latitude/Longitude
if data_type in (b'GPGLL', b'GNGGL'): # GLL, Geographic Position – Latitude/Longitude
self._parse_gpgll(args)
elif data_type == b'GPRMC': # RMC, minimum location info
elif data_type in (b'GPRMC', b'GNRMC'): # RMC, minimum location info
self._parse_gprmc(args)
elif data_type == b'GPGGA': # GGA, 3d location fix
elif data_type in (b'GPGGA', b'GNGGA'): # GGA, 3d location fix
self._parse_gpgga(args)
return True

Expand All @@ -149,15 +151,15 @@ def send_command(self, command, add_checksum=True):
Note you should NOT add the leading $ and trailing * to the command
as they will automatically be added!
"""
self._uart.write(b'$')
self._uart.write(command)
self.write(b'$')
self.write(command)
if add_checksum:
checksum = 0
for char in command:
checksum ^= char
self._uart.write(b'*')
self._uart.write(bytes('{:02x}'.format(checksum).upper(), "ascii"))
self._uart.write(b'\r\n')
self.write(b'*')
self.write(bytes('{:02x}'.format(checksum).upper(), "ascii"))
self.write(b'\r\n')

@property
def has_fix(self):
Expand All @@ -181,16 +183,36 @@ def nmea_sentence(self):
"""Return raw_sentence which is the raw NMEA sentence read from the GPS"""
return self._raw_sentence

def read(self, num_bytes):
"""Read up to num_bytes of data from the GPS directly, without parsing.
Returns a bytearray with up to num_bytes or None if nothing was read"""
return self._uart.read(num_bytes)

def write(self, bytestr):
"""Write a bytestring data to the GPS directly, without parsing
or checksums"""
return self._uart.write(bytestr)

@property
def in_waiting(self):
"""Returns number of bytes available in UART read buffer"""
return self._uart.in_waiting

def readline(self):
"""Returns a newline terminated bytearray, must have timeout set for
the underlying UART or this will block forever!"""
return self._uart.readline()

def _read_sentence(self):
# Parse any NMEA sentence that is available.
# pylint: disable=len-as-condition
# This needs to be refactored when it can be tested.

# Only continue if we have at least 32 bytes in the input buffer
if self._uart.in_waiting < 32:
if self.in_waiting < 32:
return None

sentence = self._uart.readline()
sentence = self.readline()
if sentence is None or sentence == b'' or len(sentence) < 1:
return None
try:
Expand Down Expand Up @@ -423,3 +445,64 @@ def _parse_gpgsv(self, args):
except TypeError:
pass
self.satellites_prev = self.satellites

class GPS_GtopI2C(GPS):
"""GTop-compatible I2C GPS parsing module. Can parse simple NMEA data
sentences from an I2C-capable GPS module to read latitude, longitude, and more.
"""
def __init__(self, i2c_bus, *, address=_GPSI2C_DEFAULT_ADDRESS, debug=False,
timeout=5):
import adafruit_bus_device.i2c_device as i2c_device
super().__init__(None, debug) # init the parent with no UART
self._i2c = i2c_device.I2CDevice(i2c_bus, address)
self._lastbyte = None
self._charbuff = bytearray(1)
self._internalbuffer = []
self._timeout = timeout

def read(self, num_bytes=1):
"""Read up to num_bytes of data from the GPS directly, without parsing.
Returns a bytearray with up to num_bytes or None if nothing was read"""
result = []
for _ in range(num_bytes):
with self._i2c as i2c:
# we read one byte at a time, verify it isnt part of a string of
# 'stuffed' newlines and then append to our result array for byteification
i2c.readinto(self._charbuff)
char = self._charbuff[0]
if (char == ord('\n')) and (self._lastbyte != ord('\r')):
continue # skip duplicate \n's!
result.append(char)
self._lastbyte = char # keep track of the last character approved
return bytearray(result)

def write(self, bytestr):
"""Write a bytestring data to the GPS directly, without parsing
or checksums"""
with self._i2c as i2c:
i2c.write(bytestr)

@property
def in_waiting(self):
"""Returns number of bytes available in UART read buffer, always 32
since I2C does not have the ability to know how much data is available"""
return 32

def readline(self):
"""Returns a newline terminated bytearray, must have timeout set for
the underlying UART or this will block forever!"""
timeout = time.monotonic() + self._timeout
while timeout > time.monotonic():
# check if our internal buffer has a '\n' termination already
if self._internalbuffer and (self._internalbuffer[-1] == ord('\n')):
break
char = self.read(1)
if not char:
continue
self._internalbuffer.append(char[0])
#print(bytearray(self._internalbuffer))
if self._internalbuffer and self._internalbuffer[-1] == ord('\n'):
ret = bytearray(self._internalbuffer)
self._internalbuffer = [] # reset the buffer to empty
return ret
return None # no completed data yet
29 changes: 0 additions & 29 deletions examples/gps_computer_datalogging.py

This file was deleted.

69 changes: 43 additions & 26 deletions examples/gps_datalogging.py
@@ -1,52 +1,69 @@
# Simple GPS datalogging demonstration.
# This actually doesn't even use the GPS library and instead just reads raw
# NMEA sentences from the GPS unit and dumps them to a file on an SD card
# (recommended) or internal storage (be careful as only a few kilobytes to
# megabytes are available). Before writing to internal storage you MUST
# carefully follow the steps in this guide to enable writes to the internal
# filesystem:
# This example uses the GPS library and to read raw NMEA sentences
# over I2C or UART from the GPS unit and dumps them to a file on an SD card
# (recommended), microcontroller internal storage (be careful as only a few
# kilobytes are available), or to a filesystem.
# If you are using a microcontroller, before writing to internal storage you
# MUST carefully follow the steps in this guide to enable writes to the
# internal filesystem:
# https://learn.adafruit.com/adafruit-ultimate-gps-featherwing/circuitpython-library
import board
import busio

import adafruit_gps

# Path to the file to log GPS data. By default this will be appended to
# which means new lines are added at the end and all old data is kept.
# Change this path to point at internal storage (like '/gps.txt') or SD
# card mounted storage ('/sd/gps.txt') as desired.
LOG_FILE = '/gps.txt' # Example for writing to internal path /gps.txt
#LOG_FILE = '/sd/gps.txt' # Example for writing to SD card path /sd/gps.txt
LOG_FILE = 'gps.txt' # Example for writing to internal path gps.txt

# File more for opening the log file. Mode 'ab' means append or add new lines
# to the end of the file rather than erasing it and starting over. If you'd
# like to erase the file and start clean each time use the value 'wb' instead.
LOG_MODE = 'ab'

# Define RX and TX pins for the board's serial port connected to the GPS.
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
RX = board.RX
TX = board.TX

# If writing to SD card customize and uncomment these lines to import the
# necessary library and initialize the SD card:
#SD_CS_PIN = board.SD_CS # CS for SD card (SD_CS is for Feather Adalogger)
#import adafruit_sdcard
#spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
#sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
#sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
#vfs = storage.VfsFat(sdcard)
#storage.mount(vfs, '/sd') # Mount SD card under '/sd' path in filesystem.
# If writing to SD card on a microcontroller customize and uncomment these
# lines to import the necessary library and initialize the SD card:
# NOT for use with a single board computer like Raspberry Pi!
"""
import adafruit_sdcard
import digitalio
import storage

SD_CS_PIN = board.D10 # CS for SD card using Adalogger Featherwing
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, '/sd') # Mount SD card under '/sd' path in filesystem.
LOG_FILE = '/sd/gps.txt' # Example for writing to SD card path /sd/gps.txt
"""

# Create a serial connection for the GPS connection using default speed and
# a slightly higher timeout (GPS modules typically update once a second).
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

# If using a USB/Serial converter, use pyserial and update the serial
# port name to match the serial connection for the GPS!
#import serial
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)

# If using I2C, we'll create an I2C interface to talk to using default pins
#i2c = busio.I2C(board.SCL, board.SDA)

# Create a GPS module instance.
gps = adafruit_gps.GPS(uart) # Use UART/pyserial
#gps = adafruit_gps.GPS_GtopI2C(i2c) # Use I2C interface

# Main loop just reads data from the GPS module and writes it back out to
# the output file while also printing to serial output.
with open(LOG_FILE, LOG_MODE) as outfile:
while True:
sentence = uart.readline()
sentence = gps.readline()
if not sentence:
continue
print(str(sentence, 'ascii').strip())
outfile.write(sentence)
outfile.flush()
23 changes: 11 additions & 12 deletions examples/gps_echotest.py
@@ -1,29 +1,28 @@
# Simple GPS module demonstration.
# Will print NMEA sentences received from the GPS, great for testing connection
# Uses the GPS only to send some commands, then reads directly from UART
# Uses the GPS to send some commands, then reads directly from the GPS
import time
import board
import busio

import adafruit_gps


# Define RX and TX pins for the board's serial port connected to the GPS.
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
RX = board.RX
TX = board.TX

# Create a serial connection for the GPS connection using default speed and
# a slightly higher timeout (GPS modules typically update once a second).
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

# for a computer, use the pyserial library for uart access
#import serial
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)

# If using I2C, we'll create an I2C interface to talk to using default pins
#i2c = busio.I2C(board.SCL, board.SDA)

# Create a GPS module instance.
gps = adafruit_gps.GPS(uart)
gps = adafruit_gps.GPS(uart) # Use UART/pyserial
#gps = adafruit_gps.GPS_GtopI2C(i2c) # Use I2C interface

# Initialize the GPS module by changing what data it sends and at what rate.
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and
Expand Down Expand Up @@ -52,7 +51,7 @@
# Main loop runs forever printing data as it comes in
timestamp = time.monotonic()
while True:
data = uart.read(32) # read up to 32 bytes
data = gps.read(32) # read up to 32 bytes
# print(data) # this is a bytearray type

if data is not None:
Expand Down
19 changes: 9 additions & 10 deletions examples/gps_simpletest.py
Expand Up @@ -7,23 +7,22 @@

import adafruit_gps


# Define RX and TX pins for the board's serial port connected to the GPS.
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
RX = board.RX
TX = board.TX

# Create a serial connection for the GPS connection using default speed and
# a slightly higher timeout (GPS modules typically update once a second).
uart = busio.UART(TX, RX, baudrate=9600, timeout=30)
# These are the defaults you should use for the GPS FeatherWing.
# For other boards set RX = GPS module TX, and TX = GPS module RX pins.
uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

# for a computer, use the pyserial library for uart access
#import serial
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)
#uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)

# If using I2C, we'll create an I2C interface to talk to using default pins
#i2c = busio.I2C(board.SCL, board.SDA)

# Create a GPS module instance.
gps = adafruit_gps.GPS(uart, debug=False)
gps = adafruit_gps.GPS(uart, debug=False) # Use UART/pyserial
#gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface

# Initialize the GPS module by changing what data it sends and at what rate.
# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and
Expand Down