Skip to content

Commit

Permalink
Merge branch 'Knio:master' into trimble
Browse files Browse the repository at this point in the history
  • Loading branch information
kamiccolo authored Jan 26, 2024
2 parents 43cdcb1 + f88b6cb commit 7f5267d
Show file tree
Hide file tree
Showing 33 changed files with 1,663 additions and 135 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
relative_files = True
54 changes: 54 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: pynmea2
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python: ["3"]
os: ["ubuntu-latest"]
include:
- {python: "3.8", os: "ubuntu-22.04"}
- {python: "3.9", os: "ubuntu-22.04"}
- {python: "3.10", os: "ubuntu-22.04"}
- {python: "3.11", os: "ubuntu-22.04"}
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pytest
python -m pip install flake8
python -m pip install importlib_metadata
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=80 --statistics
- name: Build and test
run: |
python setup.py sdist --formats=zip
pip install dist/pynmea2*.zip
pytest
- name: Coveralls
env:
COVERAGE_RCFILE: ".github/workflows/.coveragerc"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -m pip install "coverage"
python -m pip install "coveralls"
coverage run --source=pynmea2 -m pytest
python -m coveralls --service=github || true
30 changes: 0 additions & 30 deletions .travis.yml

This file was deleted.

2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
include README.md
include LICENSE.txt
include LICENSE
recursive-include test *
recursive-include examples *
global-exclude __pycache__
Expand Down
17 changes: 11 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

test:
py.test

publish:
python setup.py sdist upload
test:
python2 -m pytest .
python3 -m pytest .

publish: test
rm dist/ -r
python3 setup.py sdist
python3 setup.py bdist_wheel
python3 -m twine upload dist/*

.PHONY: test publish
70 changes: 47 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ pynmea2

The `pynmea2` homepage is located at http://github.com/Knio/pynmea2

### Compatibility

### Compatibility
`pynmea2` is compatable with Python 2.7 and Python 3.4+

`pynmea2` is compatable with Python 2.7 and Python 3.3

[![Build Status](https://travis-ci.org/Knio/pynmea2.png?branch=master)](https://travis-ci.org/Knio/pynmea2)
[![Coverage Status](https://coveralls.io/repos/Knio/pynmea2/badge.png?branch=master)](https://coveralls.io/r/Knio/pynmea2?branch=master)
[![Code Health](https://landscape.io/github/Knio/pynmea2/master/landscape.svg?style=flat)](https://landscape.io/github/Knio/pynmea2/master)
![Python version](https://img.shields.io/pypi/pyversions/pynmea2.svg?style=flat)
[![Build status](https://github.com/Knio/pynmea2/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Knio/pynmea2/actions/workflows/ci.yml?query=branch%3Amaster+)
[![Coverage status](https://img.shields.io/coveralls/github/Knio/pynmea2/master.svg?style=flat)](https://coveralls.io/r/Knio/pynmea2?branch=master)

### Installation

Expand All @@ -23,7 +22,8 @@ The recommended way to install `pynmea2` is with

pip install pynmea2

[![PyPI version](https://badge.fury.io/py/pynmea2.png)](http://badge.fury.io/py/pynmea2)
[![PyPI version](https://img.shields.io/pypi/v/pynmea2.svg?style=flat)](https://pypi.org/project/pynmea2/)
[![PyPI downloads](https://img.shields.io/pypi/dm/pynmea2.svg?style=flat)](https://pypi.org/project/pynmea2/)

Parsing
-------
Expand Down Expand Up @@ -97,6 +97,7 @@ Generating
You can create a `NMEASentence` object by calling the constructor with talker, message type, and data fields:

```python
>>> import pynmea2
>>> msg = pynmea2.GGA('GP', 'GGA', ('184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000'))
```

Expand All @@ -108,28 +109,51 @@ and generate a NMEA string from a `NMEASentence` object:
'$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D'
```

Streaming
---------

`pynmea2` can also process streams of NMEA sentences like so, by feeding chunks of data
manually:
File reading example
--------

See [examples/read_file.py](/examples/read_file.py)

```python
streamreader = pynmea2.NMEAStreamReader()
while 1:
data = input.read()
for msg in streamreader.next(data):
print msg
import pynmea2

file = open('examples/data.log', encoding='utf-8')

for line in file.readlines():
try:
msg = pynmea2.parse(line)
print(repr(msg))
except pynmea2.ParseError as e:
print('Parse error: {}'.format(e))
continue
```

or given a file-like device, automatically:

pySerial device example
---------

See [examples/read_serial.py](/examples/read_serial.py)

```python
streamreader = pynmea2.NMEAStreamReader(input)
while 1:
for msg in streamreader.next():
print msg
```
import io

import pynmea2
import serial


If your stream is noisy and contains errors, you can set some basic error handling with the [`errors` parameter of the `NMEAStreamReader` constructor.](pynmea2/stream.py#L12)
ser = serial.Serial('/dev/ttyS1', 9600, timeout=5.0)
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))

while 1:
try:
line = sio.readline()
msg = pynmea2.parse(line)
print(repr(msg))
except serial.SerialException as e:
print('Device error: {}'.format(e))
break
except pynmea2.ParseError as e:
print('Parse error: {}'.format(e))
continue
```
5 changes: 5 additions & 0 deletions examples/data.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D
$GPRTE,2,1,c,0,PBRCPK,PBRTO,PTELGR,PPLAND,PYAMBU,PPFAIR,PWARRN,PMORTL,PLISMR*73
$GPR00,A,B,C*29
foobar
$IIMWV,271.0,R,000.2,N,A*3B
103 changes: 103 additions & 0 deletions examples/nmea2gpx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'''
Convert a NMEA ascii log file into a GPX file
'''

import argparse
import datetime
import logging
import pathlib
import re
import xml.dom.minidom

log = logging.getLogger(__name__)

try:
import pynmea2
except ImportError:
import sys
import pathlib
p = pathlib.Path(__file__).parent.parent
sys.path.append(str(p))
log.info(sys.path)
import pynmea2


def main():
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('nmea_file')

args = parser.parse_args()
nmea_file = pathlib.Path(args.nmea_file)

if m := re.match(r'^(\d{2})(\d{2})(\d{2})', nmea_file.name):
date = datetime.date(year=2000 + int(m.group(1)), month=int(m.group(2)), day=int(m.group(3)))
log.debug('date parsed from filename: %r', date)
else:
date = None

author = 'https://github.com/Knio/pynmea2'
doc = xml.dom.minidom.Document()
doc.appendChild(root := doc.createElement('gpx'))
root.setAttribute('xmlns', "http://www.topografix.com/GPX/1/1")
root.setAttribute('version', "1.1")
root.setAttribute('creator', author)
root.setAttribute('xmlns', "http://www.topografix.com/GPX/1/1")
root.setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance")
root.setAttribute('xsi:schemaLocation', "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd")

root.appendChild(meta := doc.createElement('metadata'))
root.appendChild(trk := doc.createElement('trk'))
meta.appendChild(meta_name := doc.createElement('name'))
meta.appendChild(meta_author := doc.createElement('author'))
trk.appendChild(trk_name := doc.createElement('name'))
trk.appendChild(trkseg := doc.createElement('trkseg'))
meta_name.appendChild(doc.createTextNode(nmea_file.name))
trk_name. appendChild(doc.createTextNode(nmea_file.name))
meta_author.appendChild(author_link := doc.createElement('link'))
author_link.setAttribute('href', author)
author_link.appendChild(author_text := doc.createElement('text'))
author_link.appendChild(author_type := doc.createElement('type'))
author_text.appendChild(doc.createTextNode('Pynmea2'))
author_type.appendChild(doc.createTextNode('text/html'))

for line in open(args.nmea_file):
try:
msg = pynmea2.parse(line)
except Exception as e:
log.warning('Couldn\'t parse line: %r', e)
continue

if not (hasattr(msg, 'latitude') and hasattr(msg, 'longitude')):
continue

# if not hasattr(msg, 'altitude'):
# continue

trkseg.appendChild(trkpt := doc.createElement('trkpt'))

trkpt.setAttribute('lat', f'{msg.latitude:.6f}')
trkpt.setAttribute('lon', f'{msg.longitude:.6f}')
if hasattr(msg, 'altitude'):
trkpt.appendChild(ele := doc.createElement('ele'))
ele.appendChild(doc.createTextNode(f'{msg.altitude:.3f}'))

# TODO try msg.datetime

if date:
trkpt.appendChild(time := doc.createElement('time'))
dt = datetime.datetime.combine(date, msg.timestamp)
dts = dt.isoformat(timespec='milliseconds').replace('+00:00', 'Z')
time.appendChild(doc.createTextNode(dts))

xml_data = doc.toprettyxml(
indent=' ',
newl='\n',
encoding='utf8',
).decode('utf8')
print(xml_data)



if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
main()
11 changes: 11 additions & 0 deletions examples/read_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pynmea2

file = open('examples/data.log', encoding='utf-8')

for line in file.readlines():
try:
msg = pynmea2.parse(line)
print(repr(msg))
except pynmea2.ParseError as e:
print('Parse error: {}'.format(e))
continue
20 changes: 20 additions & 0 deletions examples/read_serial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import io

import pynmea2
import serial


ser = serial.Serial('/dev/ttyS1', 9600, timeout=5.0)
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))

while 1:
try:
line = sio.readline()
msg = pynmea2.parse(line)
print(repr(msg))
except serial.SerialException as e:
print('Device error: {}'.format(e))
break
except pynmea2.ParseError as e:
print('Parse error: {}'.format(e))
continue
32 changes: 0 additions & 32 deletions examples/serial.py

This file was deleted.

Loading

0 comments on commit 7f5267d

Please sign in to comment.