././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7397547 pynmea2-1.19.0/0000755000175100017510000000000000000000000011340 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/LICENSE0000664000175100017510000000200400000000000012343 0ustar00tomtomPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/MANIFEST.in0000664000175100017510000000021000000000000013071 0ustar00tomtominclude README.md include LICENSE recursive-include test * recursive-include examples * global-exclude __pycache__ global-exclude *.pyc ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7397547 pynmea2-1.19.0/PKG-INFO0000644000175100017510000000214200000000000012434 0ustar00tomtomMetadata-Version: 1.1 Name: pynmea2 Version: 1.19.0 Summary: Python library for the NMEA 0183 protcol Home-page: https://github.com/Knio/pynmea2 Author: Tom Flanagan Author-email: tom@zkpq.ca License: MIT Description: UNKNOWN Keywords: python nmea gps parse parsing nmea0183 0183 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Scientific/Engineering :: GIS Classifier: Topic :: Software Development :: Libraries :: Python Modules ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/README.md0000644000175100017510000001112500000000000012617 0ustar00tomtompynmea2 ======= `pynmea2` is a python library for the [NMEA 0183](http://en.wikipedia.org/wiki/NMEA_0183) protocol `pynmea2` is based on [`pynmea`](https://code.google.com/p/pynmea/) by Becky Lewis The `pynmea2` homepage is located at http://github.com/Knio/pynmea2 ### Compatibility `pynmea2` is compatable with Python 2.7 and Python 3.4+ ![Python version](https://img.shields.io/pypi/pyversions/pynmea2.svg?style=flat) [![Build Status](https://www.travis-ci.com/Knio/pynmea2.svg?branch=master)](https://www.travis-ci.com/Knio/pynmea2) [![Coverage status](https://img.shields.io/coveralls/github/Knio/pynmea2/master.svg?style=flat)](https://coveralls.io/r/Knio/pynmea2?branch=master) ### Installation The recommended way to install `pynmea2` is with [pip](http://pypi.python.org/pypi/pip/): pip install 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 ------- You can parse individual NMEA sentences using the `parse(data, check=False)` function, which takes a string containing a NMEA 0183 sentence and returns a `NMEASentence` object. Note that the leading '$' is optional and trailing whitespace is ignored when parsing a sentence. With `check=False`, `parse` will accept NMEA messages that do not have checksums, however it will still raise `pynmea2.ChecksumError` if they are present. `check=True` will also raise `ChecksumError` if the checksum is missing. Example: ```python >>> import pynmea2 >>> msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D") >>> msg ``` The `NMEASentence` object has different properties, depending on its sentence type. The `GGA` message has the following properties: ```python >>> msg.timestamp datetime.time(18, 43, 53) >>> msg.lat '1929.045' >>> msg.lat_dir 'S' >>> msg.lon '02410.506' >>> msg.lon_dir 'E' >>> msg.gps_qual '1' >>> msg.num_sats '04' >>> msg.horizontal_dil '2.6' >>> msg.altitude 100.0 >>> msg.altitude_units 'M' >>> msg.geo_sep '-33.9' >>> msg.geo_sep_units 'M' >>> msg.age_gps_data '' >>> msg.ref_station_id '0000' ``` Additional properties besides the ones explicitly in the message data may also exist. For example, `latitude` and `longitude` properties exist as helpers to access the geographic coordinates as python floats ([DD](http://en.wikipedia.org/wiki/Decimal_degrees), "decimal degrees") instead of the DDDMM.MMMM ("Degrees, minutes, seconds") format used in the NMEA protocol. `latitude_minutes`, `latitude_seconds`, `longitude_minutes`, and `longitude_seconds` are also supported and allow easy creation of differently formatted location strings. ```python >>> msg.latitude -19.4840833333 >>> msg.longitude 24.1751 >>> '%02d°%07.4f′' % (msg.latitude, msg.latitude_minutes) '-19°29.0450′' >>> '%02d°%02d′%07.4f″' % (msg.latitude, msg.latitude_minutes, msg.latitude_seconds) "-19°29′02.7000″" ``` 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')) ``` and generate a NMEA string from a `NMEASentence` object: ```python >>> str(msg) '$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D' ``` File reading example -------- See [examples/read_file.py](/examples/read_file.py) ```python 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 ``` pySerial device example --------- See [examples/read_serial.py](/examples/read_serial.py) ```python 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 ``` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7317545 pynmea2-1.19.0/examples/0000755000175100017510000000000000000000000013156 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/examples/data.log0000644000175100017510000000032100000000000014566 0ustar00tomtom$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././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/examples/nmea2gpx.py0000644000175100017510000000651600000000000015261 0ustar00tomtom''' 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()././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/examples/read_file.py0000644000175100017510000000040600000000000015442 0ustar00tomtomimport 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/examples/read_serial.py0000644000175100017510000000070000000000000015777 0ustar00tomtomimport 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././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7357545 pynmea2-1.19.0/pynmea2/0000755000175100017510000000000000000000000012713 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/__init__.py0000664000175100017510000000065300000000000015032 0ustar00tomtom# pylint: disable=missing-docstring # pylint: disable=wildcard-import # pylint: disable=invalid-name from ._version import __version__ version = __version__ from .nmea import NMEASentence, ProprietarySentence, QuerySentence from .nmea import ChecksumError, ParseError, SentenceTypeError parse = NMEASentence.parse from .types import * from .stream import NMEAStreamReader from .nmea_file import NMEAFile ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161462.0 pynmea2-1.19.0/pynmea2/_version.py0000644000175100017510000000003000000000000015102 0ustar00tomtom__version__ = '1.19.0' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/pynmea2/nmea.py0000644000175100017510000001640100000000000014207 0ustar00tomtomimport re import operator from functools import reduce class ParseError(ValueError): def __init__(self, message, data): super(ParseError, self).__init__((message, data)) class SentenceTypeError(ParseError): pass class ChecksumError(ParseError): pass class NMEASentenceType(type): sentence_types = {} def __init__(cls, name, bases, dct): type.__init__(cls, name, bases, dct) base = bases[0] if base is object: return base.sentence_types[name] = cls cls.name_to_idx = dict((f[1], i) for i, f in enumerate(cls.fields)) # http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ NMEASentenceBase = NMEASentenceType('NMEASentenceBase', (object,), {}) class NMEASentence(NMEASentenceBase): ''' Base NMEA Sentence Parses and generates NMEA strings Examples: >>> s = NMEASentence.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D") >>> print(s) ''' sentence_re = re.compile(r''' # start of string, optional whitespace, optional '$' ^\s*\$? # message (from '$' or start to checksum or end, non-inclusve) (?P # sentence type identifier (?P # proprietary sentence (P\w{3})| # query sentence, ie: 'CCGPQ,GGA' # NOTE: this should have no data (\w{2}\w{2}Q,\w{3})| # taker sentence, ie: 'GPGGA' (\w{2}\w{3},) ) # rest of message (?P[^*]*) ) # checksum: *HH (?:[*](?P[A-F0-9]{2}))? # optional trailing whitespace \s*[\r\n]*$ ''', re.X | re.IGNORECASE) talker_re = \ re.compile(r'^(?P\w{2})(?P\w{3}),$') query_re = \ re.compile(r'^(?P\w{2})(?P\w{2})Q,(?P\w{3})$') proprietary_re = \ re.compile(r'^P(?P\w{3})$') name_to_idx = {} fields = () @staticmethod def checksum(nmea_str): return reduce(operator.xor, map(ord, nmea_str), 0) @staticmethod def parse(line, check=False): ''' parse(line) Parses a string representing a NMEA 0183 sentence, and returns a NMEASentence object Raises ValueError if the string could not be parsed, or if the checksum did not match. ''' match = NMEASentence.sentence_re.match(line) if not match: raise ParseError('could not parse data', line) # pylint: disable=bad-whitespace nmea_str = match.group('nmea_str') data_str = match.group('data') checksum = match.group('checksum') sentence_type = match.group('sentence_type').upper() data = data_str.split(',') if checksum: cs1 = int(checksum, 16) cs2 = NMEASentence.checksum(nmea_str) if cs1 != cs2: raise ChecksumError( 'checksum does not match: %02X != %02X' % (cs1, cs2), data) elif check: raise ChecksumError( 'strict checking requested but checksum missing', data) talker_match = NMEASentence.talker_re.match(sentence_type) if talker_match: talker = talker_match.group('talker') sentence = talker_match.group('sentence') cls = TalkerSentence.sentence_types.get(sentence) if not cls: # TODO instantiate base type instead of fail raise SentenceTypeError( 'Unknown sentence type %s' % sentence_type, line) return cls(talker, sentence, data) query_match = NMEASentence.query_re.match(sentence_type) if query_match and not data_str: talker = query_match.group('talker') listener = query_match.group('listener') sentence = query_match.group('sentence') return QuerySentence(talker, listener, sentence) proprietary_match = NMEASentence.proprietary_re.match(sentence_type) if proprietary_match: manufacturer = proprietary_match.group('manufacturer') cls = ProprietarySentence.sentence_types.get(manufacturer, ProprietarySentence) return cls(manufacturer, data) raise ParseError( 'could not parse sentence type: %r' % sentence_type, line) def __getattr__(self, name): #pylint: disable=invalid-name t = type(self) try: i = t.name_to_idx[name] except KeyError: raise AttributeError(name) f = t.fields[i] if i < len(self.data): v = self.data[i] else: v = '' if len(f) >= 3: if v == '': return None try: return f[2](v) except: return v else: return v def __setattr__(self, name, value): #pylint: disable=invalid-name t = type(self) if name not in t.name_to_idx: return object.__setattr__(self, name, value) i = t.name_to_idx[name] self.data[i] = str(value) def __repr__(self): #pylint: disable=invalid-name r = [] d = [] t = type(self) for i, v in enumerate(self.data): if i >= len(t.fields): d.append(v) continue name = t.fields[i][1] r.append('%s=%r' % (name, getattr(self, name))) return '<%s(%s)%s>' % ( type(self).__name__, ', '.join(r), d and ' data=%r' % d or '' ) def identifier(self): raise NotImplementedError def render(self, checksum=True, dollar=True, newline=False): res = self.identifier() + ','.join(self.data) if checksum: res += '*%02X' % NMEASentence.checksum(res) if dollar: res = '$' + res if newline: res += (newline is True) and '\r\n' or newline return res def __str__(self): return self.render() class TalkerSentence(NMEASentence): sentence_types = {} def __init__(self, talker, sentence_type, data): self.talker = talker self.sentence_type = sentence_type self.data = list(data) def identifier(self): return '%s%s,' % (self.talker, self.sentence_type) class QuerySentence(NMEASentence): sentence_types = {} def __init__(self, talker, listener, sentence_type): self.talker = talker self.listener = listener self.sentence_type = sentence_type self.data = [] def identifier(self): return '%s%sQ,%s' % (self.talker, self.listener, self.sentence_type) class ProprietarySentence(NMEASentence): sentence_types = {} def __init__(self, manufacturer, data): self.manufacturer = manufacturer self.data = list(data) def identifier(self): return 'P%s' % (self.manufacturer) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/nmea_file.py0000664000175100017510000000373600000000000015217 0ustar00tomtomtry: # pylint: disable=used-before-assignment basestring = basestring except NameError: # py3 basestring = str from .nmea import NMEASentence class NMEAFile(object): """ Reads NMEA sentences from a file similar to a standard python file object. """ def __init__(self, f, *args, **kwargs): super(NMEAFile, self).__init__() if isinstance(f, basestring) or args or kwargs: self._file = self.open(f, *args, **kwargs) else: self._file = f self._context = None def open(self, fp, mode='r'): """ Open the NMEAFile. """ self._file = open(fp, mode=mode) return self._file def close(self): """ Close the NMEAFile. """ self._file.close() def __iter__(self): """ Iterate through the file yielding NMEASentences :return: """ for line in self._file: yield self.parse(line) def __enter__(self): if hasattr(self._file, '__enter__'): self._context = self._file.__enter__() return self def __exit__(self, exc_type, exc_val, exc_tb): if self._context: ctx = self._context self._context = None ctx.__exit__(exc_type, exc_val, exc_tb) def next(self): """ Iterate through the file object returning NMEASentence objects :return: NMEASentence """ data = self._file.readline() return self.parse(data) def parse(self, s): return NMEASentence.parse(s) def readline(self): """ Return the next NMEASentence in the file object :return: NMEASentence """ data = self._file.readline() s = self.parse(data) return s def read(self): """ Return a list of NMEASentence objects for each line in the file :return: list of NMEASentence objects """ return [s for s in self] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/pynmea2/nmea_utils.py0000644000175100017510000001057200000000000015432 0ustar00tomtom#pylint: disable=invalid-name import datetime import re # python 2.7 backport if not hasattr(datetime, 'timezone'): class UTC(datetime.tzinfo): def utcoffset(self, dt): return datetime.timedelta(0) class timezone(object): utc = UTC() datetime.timezone = timezone def valid(s): return s == 'A' def timestamp(s): ''' Converts a timestamp given in "hhmmss[.ss]" ASCII text format to a datetime.time object ''' ms_s = s[6:] ms = ms_s and int(float(ms_s) * 1000000) or 0 t = datetime.time( hour=int(s[0:2]), minute=int(s[2:4]), second=int(s[4:6]), microsecond=ms, tzinfo=datetime.timezone.utc) return t def datestamp(s): ''' Converts a datestamp given in "DDMMYY" ASCII text format to a datetime.datetime object ''' return datetime.datetime.strptime(s, '%d%m%y').date() def dm_to_sd(dm): ''' Converts a geographic co-ordinate given in "degrees/minutes" dddmm.mmmm format (eg, "12319.943281" = 123 degrees, 19.943281 minutes) to a signed decimal (python float) format ''' # '12319.943281' if not dm or dm == '0': return 0. r = re.match(r'^(\d+)(\d\d\.\d+)$', dm) if not r: raise ValueError("Geographic coordinate value '{}' is not valid DDDMM.MMM".format(dm)) d, m = r.groups() return float(d) + float(m) / 60 class LatLonFix(object): '''Mixin to add `latitude` and `longitude` properties as signed decimals to NMEA sentences which have co-ordinates given as degrees/minutes (lat, lon) and cardinal directions (lat_dir, lon_dir)''' #pylint: disable=no-member @property def latitude(self): '''Latitude in signed degrees (python float)''' sd = dm_to_sd(self.lat) if self.lat_dir == 'N': return +sd elif self.lat_dir == 'S': return -sd else: return 0. @property def longitude(self): '''Longitude in signed degrees (python float)''' sd = dm_to_sd(self.lon) if self.lon_dir == 'E': return +sd elif self.lon_dir == 'W': return -sd else: return 0. @staticmethod def _minutes(x): return abs(x * 60.) % 60. @staticmethod def _seconds(x): return abs(x * 3600.) % 60. @property def latitude_minutes(self): return self._minutes(self.latitude) @property def longitude_minutes(self): return self._minutes(self.longitude) @property def latitude_seconds(self): return self._seconds(self.latitude) @property def longitude_seconds(self): return self._seconds(self.longitude) class DatetimeFix(object): #pylint: disable=no-member @property def datetime(self): return datetime.datetime.combine(self.datestamp, self.timestamp) class ValidStatusFix(object): #pylint: disable=no-member @property def is_valid(self): return self.status == 'A' class ValidRMCStatusFix(ValidStatusFix): #pylint: disable=no-member @property def is_valid(self): status = super(ValidRMCStatusFix, self).is_valid if self.name_to_idx["mode_indicator"] < len(self.data): status &= self.mode_indicator in tuple('ADEFMPRS') if self.name_to_idx["nav_status"] < len(self.data): status &= self.nav_status in tuple('SCU') return status class ValidGSAFix(object): #pylint: disable=no-member @property def is_valid(self): return int(self.mode_fix_type) in [2, 3] class ValidGGAFix(object): #pylint: disable=no-member @property def is_valid(self): return self.gps_qual in range(1,6) class ValidVBWFix(object): #pylint: disable=no-member @property def is_valid(self): return self.data_validity_water_spd == self.data_validity_grnd_spd == 'A' class TZInfo(datetime.tzinfo): def __init__(self, hh, mm): self.hh = hh self.mm = mm super(TZInfo, self).__init__() def tzname(self, dt): return '' def dst(self, dt): return datetime.timedelta(0) def utcoffset(self, dt): return datetime.timedelta(hours=self.hh, minutes=self.mm) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/seatalk_utils.py0000664000175100017510000000160600000000000016136 0ustar00tomtom# pylint: disable=invalid-name class SeaTalk(object): '''Mixin to add Seatalk functionality. Based on Thomas knauf's work http://www.thomasknauf.de/seatalk.htm''' byte_to_command = { '00': 'Depth below transducer', '01': 'Equipment ID', '05': 'Engine RPM and PITCH', '10': 'Apparent Wind Angle', '11': 'Apparent Wind Speed', '20': 'Speed through water', '50': 'LAT position', '51': 'LON position', '52': 'Speed over Ground', '53': 'Course over Ground', '82': 'Target waypoint name', '84': 'Compass heading Autopilot course and Rudder position', '9C': 'Compass heading and Rudder position' } # pylint: disable=no-member @property def command_name(self): '''Get seatalk command's meaning''' return self.byte_to_command.get(self.cmd, 'Unknown Command') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549225858.0 pynmea2-1.19.0/pynmea2/stream.py0000664000175100017510000000460300000000000014565 0ustar00tomtomfrom __future__ import unicode_literals from . import nmea __all__ = ['NMEAStreamReader'] ERRORS = ('raise', 'yield', 'ignore') class NMEAStreamReader(object): ''' Reads NMEA sentences from a stream. ''' def __init__(self, stream=None, errors='raise'): ''' Create NMEAStreamReader object. `stream`: file-like object to read from, can be omitted to pass data to `next` manually. must support `.readline()` which returns a string `errors`: behaviour when a parse error is encountered. can be one of: `'raise'` (default) raise an exception immediately `'yield'` yield the ParseError as an element in the stream, and continue reading at the next line `'ignore'` completely ignore and suppress the error, and continue reading at the next line ''' if errors not in ERRORS: raise ValueError('errors must be one of {!r} (was: {!r})' .format(ERRORS, errors)) self.errors = errors self.stream = stream self.buffer = '' def next(self, data=None): ''' consume `data` (if given, or calls `stream.read()` if `stream` was given in the constructor) and yield a list of `NMEASentence` objects parsed from the stream (may be empty) ''' if data is None: if self.stream: data = self.stream.readline() else: return lines = (self.buffer + data).split('\n') self.buffer = lines.pop() for line in lines: try: msg = nmea.NMEASentence.parse(line) yield msg except nmea.ParseError as e: if self.errors == 'raise': raise e if self.errors == 'yield': yield e if self.errors == 'ignore': pass __next__ = next def __iter__(self): ''' Support the iterator protocol. This allows NMEAStreamReader object to be used in a for loop. for batch in NMEAStreamReader(stream): for msg in batch: print msg ''' return self ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7357545 pynmea2-1.19.0/pynmea2/types/0000755000175100017510000000000000000000000014057 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/types/__init__.py0000664000175100017510000000013200000000000016166 0ustar00tomtom# pylint: disable=wildcard-import from .talker import * from .proprietary import * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7357545 pynmea2-1.19.0/pynmea2/types/proprietary/0000755000175100017510000000000000000000000016437 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/pynmea2/types/proprietary/__init__.py0000644000175100017510000000032300000000000020546 0ustar00tomtomfrom . import ash from . import grm from . import kwd from . import mgn from . import rdi from . import srf from . import sxn from . import tnl from . import ubx from . import vtx from . import nor ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/pynmea2/types/proprietary/ash.py0000644000175100017510000000754300000000000017575 0ustar00tomtom''' Support for proprietary messages from Ashtech receivers. ''' # pylint: disable=wildcard-import,unused-wildcard-import from decimal import Decimal import re from ... import nmea from ...nmea_utils import * class ASH(nmea.ProprietarySentence): ''' Generic Ashtech Response Message ''' sentence_types = {} def __new__(_cls, manufacturer, data): ''' Return the correct sentence type based on the first field ''' sentence_type = data[1] name = manufacturer + 'R' + sentence_type if name not in _cls.sentence_types: # ASHRATT does not have a sentence type if ASHRATT.match(data): return super(ASH, ASHRATT).__new__(ASHRATT) cls = _cls.sentence_types.get(name, ASH) return super(ASH, cls).__new__(cls) class ASHRATT(ASH): ''' RT300 proprietary attitude sentence ''' @staticmethod def match(data): return re.match(r'^\d{6}\.\d{2,3}$', data[1]) def __init__(self, *args, **kwargs): self.subtype = 'ATT' super(ASHRATT, self).__init__(*args, **kwargs) fields = ( ('R', '_r'), ('Timestamp', 'timestamp', timestamp), ('Heading Angle', 'true_heading', float), ('Is True Heading', 'is_true_heading'), ('Roll Angle', 'roll', float), ('Pitch Angle', 'pitch', float), ('Heave', 'heave', float), ('Roll Accuracy Estimate', 'roll_accuracy', float), ('Pitch Accuracy Estimate', 'pitch_accuracy', float), ('Heading Accuracy Estimate', 'heading_accuracy', float), ('Aiding Status', 'aiding_status', Decimal), ('IMU Status', 'imu_status', Decimal), ) class ASHRHPR(ASH): ''' Ashtech HPR Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Timestamp', 'timestamp', timestamp), ('Heading Angle', 'heading', Decimal), ('Pitch Angle', 'pitch', Decimal), ('Roll Angle', 'roll', Decimal), ('Carrier measurement RMS', 'carrier_rms', Decimal), ('Baseline measurement RMS', 'baseline_rms', Decimal), ('Integer Ambiguity', 'integer_ambiguity'), ('Mode', 'mode'), ('Status', 'status'), ('PDOP', 'pdop', float), ) class ASHRLTN(ASH): ''' Ashtech LTN Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Latency (ms)', 'latency', int), ) class ASHRPOS(ASH, LatLonFix): ''' Ashtech POS Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Solution Type', 'mode', int), ('Satellites used in Solution', 'sat_count', int), ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude'), ('Empty', '__'), ("True Track/Course Over Ground", "course", float), ("Speed Over Ground", "spd_over_grnd", float), ('Vertical Velocity', 'vertical_velocity', Decimal), ('PDOP', 'pdop', float), ('HDOP', 'hdop', float), ('VDOP', 'vdop', float), ('TDOP', 'tdop', float), ('Base station ID', 'station_id', int) ) class ASHRVEL(ASH): ''' Ashtech VEL Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('ENU', 'enu', int), ('Timestamp', 'timestamp', timestamp), ('Easting', 'easting', Decimal), ('Northing', 'northing', Decimal), ('Vertical Velocity', 'vertical', Decimal), ('Easting RMS', 'easting_rms', Decimal), ('Northing RMS', 'northing_rms', Decimal), ('Vertical RMS', 'vertical_rms', Decimal), ('Applied effective velocity smoothing interval (ms)', 'smoothing', Decimal), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/pynmea2/types/proprietary/grm.py0000644000175100017510000000427400000000000017605 0ustar00tomtom# Garmin from decimal import Decimal from ... import nmea class GRM(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(GRM, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(GRM, self).__init__(manufacturer, data) class GRME(GRM): """ GARMIN Estimated position error """ fields = ( ("Subtype", "subtype"), ("Estimated Horiz. Position Error", "hpe", Decimal), ("Estimated Horiz. Position Error Unit (M)", "hpe_unit"), ("Estimated Vert. Position Error", "vpe", Decimal), ("Estimated Vert. Position Error Unit (M)", "vpe_unit"), ("Estimated Horiz. Position Error", "osepe", Decimal), ("Overall Spherical Equiv. Position Error", "osepe_unit"), ) class GRMM(GRM): """ GARMIN Map Datum """ fields = ( ("Subtype", "subtype"), ('Currently Active Datum', 'datum'), ) class GRMW(GRM): """ GARMIN Waypoint Information https://www8.garmin.com/support/pdf/NMEA_0183.pdf https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PGRMW,wname,alt,symbol,comment*99 Where, wname is waypoint name. Must match existing waypoint. alt is altitude in meters. symbol is symbol code. Hexadecimal up to FFFF. See Garmin Device Interface Specification 001-0063-00 for values of "symbol_type." comment is comment for the waypoint. *99 is checksum """ fields = ( ("Subtype", "subtype"), ("Waypoint Name", "wname"), ("Altitude", "altitude", Decimal), ("Symbol", "symbol"), ("Comment", "comment"), ) class GRMZ(GRM): """ GARMIN Altitude Information """ fields = ( ("Subtype", "subtype"), ("Altitude", "altitude", Decimal), ("Altitude Units (Feet)", "altitude_unit"), ("Positional Fix Dimension (2=user, 3=GPS)", "pos_fix_dim"), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/pynmea2/types/proprietary/kwd.py0000644000175100017510000001053700000000000017604 0ustar00tomtom# Kenwood from decimal import Decimal from datetime import date, time from ... import nmea from ... import nmea_utils class KWD(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(KWD, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(KWD, self).__init__(manufacturer, data) class KWDWPL(KWD, nmea_utils.LatLonFix, nmea_utils.DatetimeFix, nmea_utils.ValidStatusFix): """ Kenwood Waypoint Location https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PKWDWPL,hhmmss,v,ddmm.mm,ns,dddmm.mm,ew,speed,course,ddmmyy,alt,wname,ts*99 Where, hhmmss is time in UTC from the clock in the transceiver. This will be bogus if the clock was not set properly. It does not use the timestamp from a position report which could be useful. GPS Status A = active, V = void. It looks like this might be modeled after the GPS status values we see in $GPRMC. i.e. Does the transceiver know its location? I don't see how that information would be relevant in this context. I've observed this under various conditions (No GPS, GPS with/without fix) and it has always been "V." ddmm.mm,ns is latitude. N or S. dddmm.mm,ew is longitude. E or W. speed is speed over ground, knots. course is course over ground, degrees. ddmmyy is date. See comments for time. alt is altitude, meters above mean sea level. wname is the waypoint name. For an Object Report, the id is the object name. For a position report, it is the call of the sending station. An Object name can contain any printable characters. What if object name contains , or * characters? Those are field delimiter characters and it would be unfortunate if they appeared in a NMEA sentence data field. If there is a comma in the name, such as "test,5" the Kenwood TM-D710A displays it fine but we end up with an extra field. $PKWDWPL,150803,V,4237.14,N,07120.83,W,,,190316,,test,5,/'*30 If the name contains an asterisk, it doesn't show up on the display and no waypoint sentence is generated. Some other talkers substitute these two characters following the AvMap precedent. $PKWDWPL,204714,V,4237.1400,N,07120.8300,W,,,200316,,test|5,/'*61 $PKWDWPL,204719,V,4237.1400,N,07120.8300,W,,,200316,,test~6,/'*6D ts are the table and symbol. What happens if the symbol is comma or asterisk? , Boy Scouts / Girl Scouts * SnowMobile / Snow the D710A just pushes them thru without checking. These would not be parsed properly: $PKWDWPL,150753,V,4237.14,N,07120.83,W,,,190316,,test3,/,*1B $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/ **3B Other talkers do the usual substitution and the other end would need to change them back after extracting from NMEA sentence. $PKWDWPL,204704,V,4237.1400,N,07120.8300,W,,,200316,,test3,/|*41 $PKWDWPL,204709,V,4237.1400,N,07120.8300,W,,,200316,,test4,/~*49 *99 is checksum Oddly, there is no place for comment. """ fields = ( ("Subtype", "subtype"), ("Time of Receipt", "timestamp", nmea_utils.timestamp), ("GPS Status (Void)","status"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Speed over Ground", "sog", float), ("Course over Ground", "cog", float), ("Date", "datestamp", nmea_utils.datestamp), ("Altitude", "altitude", Decimal), ("Waypoint Name", "wname"), ("Table and Symbol", "ts"), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/pynmea2/types/proprietary/mgn.py0000644000175100017510000000313200000000000017571 0ustar00tomtom# Magellan from decimal import Decimal from ... import nmea from ... import nmea_utils class MGN(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(MGN, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(MGN, self).__init__(manufacturer, data) class MGNWPL(MGN, nmea_utils.LatLonFix): """ Magellan Waypoint Location https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99 Where, ddmm.mmmm,ns is latitude dddmm.mmmm,ew is longitude alt is altitude unit is M for meters or F for feet wname is the waypoint name comment is message or comment icon is one or two letters for icon code xx is waypoint type which is optional, not well defined, and not used in their example. *99 is checksum """ fields = ( ("Subtype", "subtype"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Altitude", "altitude", Decimal), ("Altitude Units (Feet/Meters)", "altitude_unit"), ("Waypoint Name", "wname"), ("Comment", "comment"), ("Icon", "icon"), ("Waypoint Type", "type") ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/pynmea2/types/proprietary/nor.py0000644000175100017510000002401100000000000017605 0ustar00tomtom''' Support for proprietary messages from Nortek Doppler Velocity Log (DVL). ''' from ... import ProprietarySentence, nmea_utils from datetime import datetime class NOR(ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(NOR, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(NOR, self).__init__(manufacturer, data[1:]) def identifier(self): return 'P%s,' % (self.sentence_type) ################################################################## ## ## ## DVL Bottom Track ASCII formats ## ## ## ## Invalid estimates of Velocity are set to set to -32.768. ## ## Invalid estimates of Range are set to 0.0. ## ## Invalid estimates of FOM are set to 10.0 ## ################################################################## class NORBT0(NOR, nmea_utils.DatetimeFix): # Bottom Track DF350/DF351 - NMEA $PNORBT1/$PNORBT0 # Example: $PNORBT0,1,040721,131335.3341,23.961,-48.122,-32.76800,10.00000,0.00,0x00000000*48 fields = ( ('Beam number', 'beam', int), ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Beam Velocity', 'bv', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ('Status ', 'stat'), ) class NORBT4(NOR, nmea_utils.DatetimeFix): # Bottom Track DF354/DF355 - NMEA $PNORBT3/$PNORBT4 # Example: $PNORBT4,1.234,-1.234,1.234,23.4,12.34567,12.3*09 fields = ( ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Speed of Sound', 'sound_speed', float), ('Direction', 'dir', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ) class NORBT7(NOR): # Bottom Track DF356/DF357 - NMEA $PNORBT6/$PNORBT7 # Example: $PNORBT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*39 fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ) class NORBT9(NOR): # Bottom Track DF358/DF359 - NMEA $PNORBT8/$PNORBT9 # Example: $PNORBT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*1E fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ('Status ', 'stat'), ) ################################################################## ## ## ## DVL Water Track ASCII formats ## ## ## ## Invalid estimates of Velocity are set to set to -32.768. ## ## Invalid estimates of Range are set to 0.0. ## ## Invalid estimates of FOM are set to 10.0 ## ################################################################## class NORWT4(NOR): # Water Track DF404/DF405 - NMEA $PNORWT3/$PNORWT4 # Example: $PNORWT4,1.2345,-1.2345,1.234,23.4,12.34,12.3*1C fields = ( ('Time Trigger ', 'dt1', float), ('Time NMEA', 'dt2', float), ('Speed of sound', 'sound_speed', float), ('Direction', 'dir', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ) class NORWT7(NOR): # Water Track DF406/DF407 - NMEA $PNORWT6/$PNORWT7 # Example: $PNORWT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*2C fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ) class NORWT9(NOR): # Water Track DF408/DF409 - NMEA $PNORWT8/$PNORWT9 # Example: $PNORWT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*0B fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ('Status ', 'stat'), ) ################################################################## ## ## ## DVL Current Profile ASCII formats ## ## ## ################################################################## class NORI1(NOR): # Information Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORI1,4,123456,3,30,1.00,5.00,BEAM*5B fields = ( ('Instrument type', 'it', int), ('Head ID', 'sn', int), ('Number of Beams', 'nb', int), ('Number of Cells', 'nc', int), ('Blanking Distance', 'bd', float), ('Cell Size', 'cs', float), ('Coordinate System', 'cy', str), ) class NORS1(NOR, nmea_utils.DatetimeFix): # Sensors Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORS1,161109,132455,0,34000034,23.9,1500.0,123.4,0.02,45.6,0.02,23.4,0.02,123.456,0.02,24.56*51 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Error Code', 'ec', int), ('Status Code', 'sc'), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Heading', 'heading', float), ('Heading Std. Dev.', 'heading_std', float), ('Pitch', 'pitch', float), ('Pitch Std. Dev.', 'pitch_std', float), ('Roll', 'roll', float), ('Roll Std. Dev.', 'roll_std', float), ('Pressure', 'pressure', float), ('Pressure Std. Dev.', 'pressure_std', float), ('Temperature', 'temp', float), ) class NORS4(NOR, nmea_utils.DatetimeFix): # Sensors Data DF103/DF104 # Example: $PNORS4,23.6,1530.2,0.0,0.0,0.0,0.000,23.30*66 fields = ( ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Heading', 'heading', float), ('Pitch', 'pitch', float), ('Roll', 'roll', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ) class NORC1(NOR, nmea_utils.DatetimeFix): # Current Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORC1,083013,132455,3,11.0,0.332,0.332,0.332,78.9,78.9,78.9,78,78,78*46 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Cell Number', 'cn', int), ('Cell Position', 'cp', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Velocity Z2', 'vz2', float), ('Amplitude Beam 1', 'amp1', float), ('Amplitude Beam 2', 'amp2', float), ('Amplitude Beam 3', 'amp3', float), ('Amplitude Beam 4', 'amp4', float), ('Correlation Beam 1', 'r1', int), ('Correlation Beam 2', 'r2', int), ('Correlation Beam 3', 'r3', int), ('Correlation Beam 4', 'r4', int), ('Correlation Beam 4', 'r5', int), ) class NORC4(NOR, nmea_utils.DatetimeFix): # Current Data DF103/DF104 # Example: $PNORC4,1.5,1.395,227.1,32,32*7A fields = ( ('Cell Position', 'cp', float), ('Speed', 'sp', float), ('Direction', 'dir', float), ('Correlation', 'r', int), ('Amplitude', 'amp', int), ) class NORH4(NOR, nmea_utils.DatetimeFix): # Header Data DF103/DF104 # Example: $PNORH4,161109,143459,0,204C0002*38 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Error Code', 'ec', int), ('Status Code', 'sc'), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/types/proprietary/rdi.py0000664000175100017510000000122600000000000017572 0ustar00tomtom''' Support for proprietary message(s) from RD Instruments ''' from ... import nmea class RDI(nmea.ProprietarySentence): ''' RD Instruments message. Only one sentence known? ''' sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(RDI, cls).__new__(cls) class RDID(RDI): ''' RD Instruments heading, pitch and roll data ''' fields = ( ('Subtype', 'subtype'), ("Pitch", "pitch", float), ("Roll", "roll", float), ("Heading", "heading", float) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/types/proprietary/srf.py0000664000175100017510000000234000000000000017604 0ustar00tomtom# SiRF from ... import nmea class SRF(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(SRF, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(SRF, self).__init__(manufacturer, data) class SRF103(SRF): fields = ( ("Subtype", "subtype"), ('Sentence type', 'sentence'), # 00=GGA # 01=GLL # 02=GSA # 03=GSV # 04=RMC # 05=VTG ('Command', 'command'), # 0=Set # 1=Query ('Rate', 'rate'), ('Checksum', 'checksum'), # 0=No, 1=Yes ) class SRF100(SRF): fields = ( ("Subtype", "subtype"), ('Protocol', 'protocol'), # 0 = SiRF Binary # 1 = NMEA ('Baud Rate', 'baud'), # 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 ('Data bits', 'databits'), # 8 (, 7 in NMEA) ('Stop bits', 'stopbits'), # 0, 1 ('Parity', 'parity'), # 0, 1=Odd, 2=Even ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/types/proprietary/sxn.py0000664000175100017510000000713300000000000017627 0ustar00tomtom''' Seapath Message types: $PSXN,20,horiz-qual,hgt-qual,head-qual,rp-qual*csum term $PSXN,22,gyro-calib,gyro-offs*csum term $PSXN,23,roll,pitch,head,heave*csum term $PSXN,24,roll-rate,pitch-rate,yaw-rate,vertical-vel*csum term $PSXN,21,event*csum term Where: horiz-qual: Horizontal position and velocity quality: 0 = normal, 1 = reduced performance, 2= invalid data. hgt-qual: Height and vertical velocity quality: 0 = normal, 1 = reduced performance, 2 =invalid data. head-qual: Heading quality: 0 = normal, 1 = reduced performance, 2 = invalid data. rp-qual: Roll and pitch quality: 0 = normal, 1 = reduced performance, 2 = invalid data. gyro-calib: Gyro calibration value since system start-up in degrees on format d.dd. gyro-offs: Short-term gyro offset in degrees on format d.dd. roll: Roll in degrees on format d.dd. Positive with port side up. pitch: Pitch in degrees on format d.dd. Positive with bow up. heave: Heave in metres on format d.dd. Positive down. roll-rate: Roll rate in degrees per second on format d.dd. Positive when port side is moving upwards. pitch-rate: Pitch rate in degrees per second on format d.dd. Positive when bow is moving upwards. yaw-rate: Yaw rate in degrees per second on format d.dd. Positive when bow is moving towards starboard. vertical-vel: Vertical velocity in metres per second on format d.dd. Positive when moving downwards. event: Event code: 1 = system restart. csum: Checksum (exclusive or) of all characters between, but not including, the preceding $ and * , hexadecimal (00 - FF). term: CR-LF (2 bytes, values 13 and 10). Samples: $PSXN,20,0,0,0,0*3B $PSXN,23,0.30,-0.97,298.57,0.13*1B $PSXN,26,0,44.5000,0.7800,-0.9000,NRP*6D ''' from ... import nmea class SXN(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(SXN, cls).__new__(cls) class SXN20(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Horizontal position and velocity quality', 'horiz_qual', int), ('Height and vertical velocity quality', 'hgt_qual', int), ('Heading quality', 'head_qual', int), ('Roll and pitch quality', 'rp_qual', int), ) class SXN21(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Event code: 1 = system restart.', 'event', int), ) class SXN22(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Gyro calibration value since system start-up in degrees', 'gyro_calib', float), ('Short-term gyro offset in degrees', 'gyro_ffs', float), ) class SXN23(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Roll in degrees. Positive with port side up.', 'roll', float), ('Pitch in degrees. Positive with bow up.', 'pitch', float), ('Heading, degrees true (0.00 - 359.99).', 'head', float), ('Heave in metres. Positive down.', 'heave', float) ) class SXN24(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Roll rate in degrees/second. Positive when port side is moving upwards.', 'roll_rate', float), ('Pitch rate in degrees/second. Positive when bow is moving upwards.', 'pitch_rate', float), ('Yaw rate in degrees/second. Positive when bow is moving towards starboard.', 'yaw_rate', float), ('Vertical velocity in metres/second. Positive when moving downwards.', 'vertical_vel', float) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/pynmea2/types/proprietary/tnl.py0000664000175100017510000000651400000000000017616 0ustar00tomtom# -- TRIMBLE -- # # pylint: disable=wildcard-import,unused-wildcard-import from ... import nmea from ...nmea_utils import * """ Support for proprietary messages from BD9xx recievers. Documentation: www.trimble.com/OEM_ReceiverHelp/v4.85/en/ """ class TNL(nmea.ProprietarySentence): sentence_types = {} """ Generic Trimble Message """ def __new__(_cls, manufacturer, data): ''' Return the correct sentence type based on the first field ''' sentence_type = data[0] or data[1] name = manufacturer + sentence_type cls = _cls.sentence_types.get(name, _cls) return super(TNL, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = data[0] or data[1] super(TNL, self).__init__(manufacturer, data) class TNLAVR(TNL): """ Trimble AVR Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ('Yaw Angle', 'yaw_angle'), ('Yaw', 'yaw'), ('Tilt Angle', 'tilt_angle'), ('Tilt', 'tilt'), ('Roll Angle', 'roll_angle'), ('Roll', 'roll'), ('Baseline Range', 'baseline'), ('GPS Quality', 'gps_quality'), ('PDOP', 'pdop'), ('Total number of satelites in use', 'num_sats'), ) class TNLBPQ(TNL, LatLonFix, DatetimeFix): """ Trimble BPQ Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ('Height Ellipsoid', 'height'), ('Meters', 'meters'), ('Mode fix type', 'mode_fix_type'), ('Total number of satelites in use', 'num_sats'), ) class TNLGGK(TNL, LatLonFix, DatetimeFix): """ Trimble GGK Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ('GPS Quality', 'quality'), ('Total number of satelites in use', 'num_sats'), ('DOP', 'dop'), ('Height Ellipsoid', 'height'), ('Meters', 'meters'), ('Mode fix type', 'mode_fix_type'), ) class TNLVHD(TNL, DatetimeFix): """ Trimble VHD Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ('Azimuth Angle', 'azimuth'), ('AzimuthTime', 'azdt'), ('Vertical Angle', 'vertical'), ('VerticalTime', 'vertdt'), ('Range', 'range'), ('RangeTime', 'rdt'), ('GPS Quality', 'gps_quality'), ('Total number of satelites in use', 'num_sats'), ('PDOP', 'pdop'), ) class TNLPJT(TNL): """ Trimble PJT Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Coordinate System', 'coord_name'), ('Project Name', 'project_name'), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616391507.0 pynmea2-1.19.0/pynmea2/types/proprietary/ubx.py0000644000175100017510000000412400000000000017610 0ustar00tomtom# pylint: disable=wildcard-import,unused-wildcard-import from decimal import Decimal from ... import nmea from ...nmea_utils import * # u-blox class UBX(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(UBX, cls).__new__(cls) class UBX00(UBX, LatLonFix): """ Lat/Long Position Data """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("Timestamp (UTC)", "timestamp", timestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Altitude above user datum ellipsoid", "alt_ref"), ("Navigation Status", "nav_stat"), ("Horizontal Accuracy Estimate", "h_acc"), ("Vertical Accuracy Estimate", "v_acc"), ("Speed over Ground", "sog"), ("Course over Ground", "cog"), ("Vertical Velocity", "v_vel"), ("Age of Differential Corrections", "diff_age"), ("Horizontal Dilution of Precision", "hdop"), ("Vertical Dilution of Precision", "vdop"), ("Time Dilution of Precision", "tdop"), ("Number of Satellites Used", "num_svs"), ("Reserved", "reserved") ) class UBX03(UBX): """ Satellite Status """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("Number of GNSS Satellites Tracked", "num_sv", int), ) @property def satellite_list(self): return self.data[1:] class UBX04(UBX): """ Time and Day Clock Information """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("UTC Time", "time", timestamp), ("UTC Date", "date", datestamp), ("UTC Time of Week", "utc_tow"), ("UTC Week Number", "utc_wk"), ("Leap Seconds", "leap_sec"), ("Receiver Clock Bias", "clk_bias", int), ("Receiver Clock Drift", "clk_drift", Decimal), ("Time Pulse Granularity", "tp_gran", int), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613859353.0 pynmea2-1.19.0/pynmea2/types/proprietary/vtx.py0000644000175100017510000000537400000000000017643 0ustar00tomtom# Vectronix Moskito TI (LRF) from decimal import Decimal from ... import nmea from ...nmea_utils import * class VTX(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(VTX, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(VTX, self).__init__(manufacturer, data) class VTX0002(VTX): """ Vectronix measurement: laser distance and angles (degrees) with declination """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ("Distance (meters)", "dist", float), ("Distance unit", "dist_unit"), ("Direction (degrees)", "direction", float), ("Direction unit", "direction_unit"), ("Vertical angle (degrees)", "va", float), ("Magnetic declination (degrees)", "decl", float), ("Magnetic declination ref (E/W)", "decl_ref") ) class VTX0000(VTX): """ Vectronix raw measurement: laser distance and angles (radians) without declination """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Distance (meters)", "dist", float), ("Distance unit", "dist_unit"), ("Direction (radians)", "direction", float), ("Roll angle (radians)", "roll", float), ("Vertical angle (radians)", "va", float), ("Angular units type", "angle_units") ) class VTX0020(VTX, LatLonFix): """ Vectronix self location: lat, long, altitude """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude', float), ('Altitude units', 'altitude_units') ) class VTX0012(VTX, LatLonFix): """ Vectronix target location: lat, long, altitude, gain """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude', float), ('Altitude units', 'altitude_units'), ('Gain (meters)', 'gain', float), ('Gain units', 'gain_units') ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/pynmea2/types/talker.py0000644000175100017510000010670100000000000015720 0ustar00tomtom# pylint: disable=wildcard-import,unused-wildcard-import from ..nmea import TalkerSentence from ..nmea_utils import * from ..seatalk_utils import * from collections import namedtuple from decimal import Decimal #pylint: disable=missing-docstring #pylint: disable=no-init #pylint: disable=too-few-public-methods class AAM(TalkerSentence): """ Waypoint Arrival Alarm """ fields = ( ("Arrival Circle Entered", "arrival_circ_entered"), ("Perpendicular Passed", "perp_passed"), ("Circle Radius", "circle_rad"), ("Nautical Miles", "circle_rad_unit"), ("Waypoint ID", "waypoint_id"), ) class ALM(TalkerSentence): """ GPS Almanac data """ fields = ( ("Total number of messages", "total_num_msgs"), ("Message number", "msg_num"), ("Satellite PRN number", "sat_prn_num"), # 01 - 32 ("GPS week number", "gps_week_num"), # Week since Jan 6 1980 ("SV Health, bits 17-24 of each almanac page", "sv_health"), ("Eccentricity", "eccentricity"), ("Almanac Reference Time", "alamanac_ref_time"), ("Inclination Angle", "inc_angle"), ("Rate of right ascension", "rate_right_asc"), ("Root of semi-major axis", "root_semi_major_axis"), ("Argument of perigee", "arg_perigee"), ("Longitude of ascension node", "lat_asc_node"), ("Mean anomaly", "mean_anom"), ("F0 Clock parameter", "f0_clock_param"), ("F1 Clock parameter", "f1_clock_param"), ) class APA(TalkerSentence): """ Autopilot Sentence "A" """ fields = ( ("General Status", "status_gen"), ("Cycle lock Status", "status_cycle_lock"), ("Cross Track Error Magnitude", "cross_track_err_mag"), ("Direction to Steer (L or R)", "dir_steer"), ("Cross Track Units (Nautical Miles or KM)", "cross_track_unit"), ("Arrival Circle Entered", "arr_circle_entered"), # A = True ("Perpendicular passed at waypoint", "perp_passed"), # A = True ("Bearing origin to destination", "bearing_to_dest"), ("Bearing type", "bearing_type"), # M = Magnetic, T = True ("Destination waypoint ID", "dest_waypoint_id"), ) class APB(TalkerSentence): """ Autopilot Sentence "B" """ fields = ( ("General Status", "status_gen"), ("Cycle lock Status", "status_cycle_lock"), ("Cross Track Error Magnitude", "cross_track_err_mag"), ("Direction to Steer (L or R)", "dir_steer"), ("Cross Track Units (Nautical Miles or KM)", "cross_track_unit"), ("Arrival Circle Entered", "arr_circle_entered"), # A = True ("Perpendicular passed at waypoint", "perp_passed"), # A = True ("Bearing origin to destination", "bearing_to_dest"), ("Bearing type", "bearing_type"), # M = Magnetic, T = True ("Destination waypoint ID", "dest_waypoint_id"), ("Bearing, present position to dest", "bearing_pres_dest"), ("Bearing to destination, type", "bearing_pres_dest_type"), # M = Magnetic, T = True ("Heading to steer to destination", "heading_to_dest"), ("Heading to steer to destination type", "heading_to_dest_type"), ) # M = Magnetic, T = True class BEC(TalkerSentence): """ Bearing & Distance to Waypoint, Dead Reckoning """ fields = ( ("Timestamp", "timestamp", timestamp), ("Waypoint Latitude", "waypoint_lat"), ("Waypoint Latitude direction", "waypoint_lat_dir"), ("Waypoint Longitude", "waypoint_lon"), ("Waypoint Longitude direction", "waypoint_lon_dir"), ("Bearing, true", "bearing_true"), ("Bearing True symbol", "bearing_true_sym"), # T = true ("Bearing Magnetic", "bearing_mag"), ("Bearing Magnetic symbol", "bearing_mag_sym"), ("Nautical Miles", "nautical_miles"), ("Nautical Miles symbol", "nautical_miles_sym"), ("Waypoint ID", "waypoint_id"), ("FAA mode indicator", "faa_mode"), ) class BOD(TalkerSentence): # 045.,T,023.,M,DEST,START fields = ( ('Bearing True', 'bearing_t', Decimal), ('Bearing True Type', 'bearing_t_type'), ('Bearing Magnetic', 'bearing_mag', Decimal), ('Bearing Magnetic Type', 'bearing_mag_type'), ('Destination', 'dest'), ('Start', 'start'), ) class BWC(TalkerSentence): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude of next Waypoint', 'lat_next'), ('Latitude of next Waypoint Direction', 'lat_next_direction'), ('Longitude of next Waypoint', 'lon_next'), ('Longitude of next Waypoint Direction', 'lon_next_direction'), ('True track to waypoint', 'true_track'), ('True Track Symbol', 'true_track_sym'), ('Magnetic track to waypoint', 'mag_track'), ('Magnetic Symbol', 'mag_sym'), ('Range to waypoint', 'range_next'), ('Unit of range', 'range_unit'), ('Waypoint Name', 'waypoint_name'), ) class BWR(TalkerSentence): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude of next Waypoint', 'lat_next'), ('Latitude of next Waypoint Direction', 'lat_next_direction'), ('Longitude of next Waypoint', 'lon_next'), ('Longitude of next Waypoint Direction', 'lon_next_direction'), ('True track to waypoint', 'true_track'), ('True Track Symbol', 'true_track_sym'), ('Magnetic track to waypoint', 'mag_track'), ('Magnetic Symbol', 'mag_sym'), ('Range to waypoint', 'range_next'), ('Unit of range', 'range_unit'), ('Waypoint Name', 'waypoint_name'), ) class GGA(TalkerSentence, ValidGGAFix, LatLonFix): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('GPS Quality Indicator', 'gps_qual', int), ('Number of Satellites in use', 'num_sats'), ('Horizontal Dilution of Precision', 'horizontal_dil'), ('Antenna Alt above sea level (mean)', 'altitude', float), ('Units of altitude (meters)', 'altitude_units'), ('Geoidal Separation', 'geo_sep'), ('Units of Geoidal Separation (meters)', 'geo_sep_units'), ('Age of Differential GPS Data (secs)', 'age_gps_data'), ('Differential Reference Station ID', 'ref_station_id'), ) class GNS(TalkerSentence, LatLonFix): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Mode indicator', 'mode_indicator'), ('Total number of satelites in use', 'num_sats'), ('HDROP', 'hdop'), ('Antenna altitude, meters', 'altitude'), ('Goeidal separation meters', 'geo_sep'), ('Age of diferential data', 'age_gps_data'), ('Differential reference station ID', 'diferential'), ) class GRS(TalkerSentence): """ Order of satellites will match those in the last GSA """ fields = ( ('Timestamp', 'timestamp', timestamp), ('Residuals mode', 'residuals_mode', int), ('SV 01 Residual (m)', 'sv_res_01', float), ('SV 02 Residual (m)', 'sv_res_02', float), ('SV 03 Residual (m)', 'sv_res_03', float), ('SV 04 Residual (m)', 'sv_res_04', float), ('SV 05 Residual (m)', 'sv_res_05', float), ('SV 06 Residual (m)', 'sv_res_06', float), ('SV 07 Residual (m)', 'sv_res_07', float), ('SV 08 Residual (m)', 'sv_res_08', float), ('SV 09 Residual (m)', 'sv_res_09', float), ('SV 10 Residual (m)', 'sv_res_10', float), ('SV 11 Residual (m)', 'sv_res_11', float), ('SV 12 Residual (m)', 'sv_res_12', float), ) class BWW(TalkerSentence): """ Bearing, Waypoint to Waypoint """ fields = ( ("Bearing degrees True", "bearing_deg_true"), ("Bearing degrees True Symbol", "bearing_deg_true_sym"), ("Bearing degrees Magnitude", "bearing_deg_mag"), ("Bearing degrees Magnitude Symbol", "bearing_deg_mag_sym"), ("Destination Waypoint ID", "waypoint_id_dest"), ("Origin Waypoint ID", "waypoint_id_orig"), ) class GLL(TalkerSentence, ValidStatusFix, LatLonFix): fields = ( ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Timestamp', 'timestamp', timestamp), ('Status', 'status'), # contains the 'A' or 'V' flag ("FAA mode indicator", "faa_mode"), ) class GSA(TalkerSentence, ValidGSAFix): fields = ( ('Mode', 'mode'), ('Mode fix type', 'mode_fix_type'), ('SV ID01', 'sv_id01'), ('SV ID02', 'sv_id02'), ('SV ID03', 'sv_id03'), ('SV ID04', 'sv_id04'), ('SV ID05', 'sv_id05'), ('SV ID06', 'sv_id06'), ('SV ID07', 'sv_id07'), ('SV ID08', 'sv_id08'), ('SV ID09', 'sv_id09'), ('SV ID10', 'sv_id10'), ('SV ID11', 'sv_id11'), ('SV ID12', 'sv_id12'), ('PDOP (Dilution of precision)', 'pdop'), ('HDOP (Horizontal DOP)', 'hdop'), ('VDOP (Vertical DOP)', 'vdop'), ) class GST(TalkerSentence): fields = ( ('UTC time of the GGA or GNS fix associated with this sentence.', 'timestamp', timestamp), ('RMS value of the standard deviation of the range inputs to the navigation process. Range inputs include preudoranges & DGNSS corrections.', 'rms', float), ('Standard deviation of semi-major axis of error ellipse (meters)', 'std_dev_major', float), ('Standard deviation of semi-minor axis of error ellipse (meters)', 'std_dev_minor', float), ('Orientation of semi-major axis of error ellipse (degrees from true north)', 'orientation', float), ('Standard deviation of latitude error (meters)', 'std_dev_latitude', float), ('Standard deviation of longitude error (meters)', 'std_dev_longitude', float), ('Standard deviation of altitude error (meters)', 'std_dev_altitude', float), ) class GSV(TalkerSentence): fields = ( ('Number of messages of type in cycle', 'num_messages'), ('Message Number', 'msg_num'), ('Total number of SVs in view', 'num_sv_in_view'), ('SV PRN number 1', 'sv_prn_num_1'), ('Elevation in degrees 1', 'elevation_deg_1'), # 90 max ('Azimuth, deg from true north 1', 'azimuth_1'), # 000 to 159 ('SNR 1', 'snr_1'), # 00-99 dB ('SV PRN number 2', 'sv_prn_num_2'), ('Elevation in degrees 2', 'elevation_deg_2'), # 90 max ('Azimuth, deg from true north 2', 'azimuth_2'), # 000 to 159 ('SNR 2', 'snr_2'), # 00-99 dB ('SV PRN number 3', 'sv_prn_num_3'), ('Elevation in degrees 3', 'elevation_deg_3'), # 90 max ('Azimuth, deg from true north 3', 'azimuth_3'), # 000 to 159 ('SNR 3', 'snr_3'), # 00-99 dB ('SV PRN number 4', 'sv_prn_num_4'), ('Elevation in degrees 4', 'elevation_deg_4'), # 90 max ('Azimuth, deg from true north 4', 'azimuth_4'), # 000 to 159 ('SNR 4', 'snr_4'), ) # 00-99 dB class HDG(TalkerSentence): """ NMEA 0183 standard Heading, Deviation and Variation Format: $HCHDG,<1>,<2>,<3>,<4>,<5>*hh <1> Magnetic sensor heading, degrees, to the nearest 0.1 degree. <2> Magnetic deviation, degrees east or west, to the nearest 0.1 degree. <3> E if field <2> is degrees East W if field <2> is degrees West <4> Magnetic variation, degrees east or west, to the nearest 0.1 degree. <5> E if field <4> is degrees East W if field <4> is degrees West """ fields = ( ("Heading", "heading", Decimal), ("Deviation", "deviation", Decimal), ("Deviation Direction", "dev_dir"), ("Variation", "variation", Decimal), ("Variation Direction", "var_dir"), ) class HDT(TalkerSentence): fields = ( ("Heading", "heading", Decimal), ("True", "hdg_true"), ) class RMA(TalkerSentence): fields = ( ("Data status", "data_status"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Not Used 1", "not_used_1"), ("Not Used 2", "not_used_2"), ("Speed over ground", "spd_over_grnd"), # Knots ("Course over ground", "crse_over_grnd"), ("Variation", "variation"), ("Variation Direction", "var_dir"), ) class RMB(TalkerSentence, ValidStatusFix): """ Recommended Minimum Navigation Information """ fields = ( ('Status', 'status'), # contains the 'A' or 'V' flag ("Cross Track Error", "cross_track_error"), # nautical miles, 9.9 max ("Cross Track Error, direction to corrent", "cte_correction_dir"), ("Origin Waypoint ID", "origin_waypoint_id"), ("Destination Waypoint ID", "dest_waypoint_id"), ("Destination Waypoint Latitude", "dest_lat"), ("Destination Waypoint Lat Direction", "dest_lat_dir"), ("Destination Waypoint Longitude", "dest_lon"), ("Destination Waypoint Lon Direction", "dest_lon_dir"), ("Range to Destination", "dest_range"), # Nautical Miles ("True Bearing to Destination", "dest_true_bearing"), ("Velocity Towards Destination", "dest_velocity"), # Knots ("Arrival Alarm", "arrival_alarm"), ) # A = Arrived, V = Not arrived class RMC(TalkerSentence, ValidRMCStatusFix, LatLonFix, DatetimeFix): """ Recommended Minimum Specific GPS/TRANSIT Data """ fields = ( ("Timestamp", "timestamp", timestamp), ('Status', 'status'), # contains the 'A' or 'V' flag ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Speed Over Ground", "spd_over_grnd", float), ("True Course", "true_course", float), ("Datestamp", "datestamp", datestamp), ("Magnetic Variation", "mag_variation"), ("Magnetic Variation Direction", "mag_var_dir"), ("Mode Indicator", "mode_indicator"), ("Navigational Status", "nav_status"), ) class RTE(TalkerSentence): """ Routes """ fields = ( ("Number of sentences in sequence", "num_in_seq"), ("Sentence Number", "sen_num"), ("Start Type", "start_type"), # The first in the list is either current route or waypoint ("Name or Number of Active Route", "active_route_id"), ) @property def waypoint_list(self): return self.data[4:] @waypoint_list.setter def waypoint_list(self, val): self.data[4:] = val class R00(TalkerSentence): fields = () @property def waypoint_list(self): return self.data[:] @waypoint_list.setter def waypoint_list(self, val): self.data[:] = val class STN(TalkerSentence): """ NOTE: No real data could be found for examples of the actual spec so it is a guess that there may be a checksum on the end """ fields = ( ("Talker ID Number", "talker_id_num"), ) # 00 - 99 class TRF(TalkerSentence): """ Transit Fix Data """ fields = ( ("Timestamp (UTC)", "timestamp", timestamp), ("Date (DD/MM/YY", "date"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Elevation Angle", "ele_angle"), ("Number of Iterations", "num_iterations"), ("Number of Doppler Intervals", "num_doppler_intervals"), ("Update Distance", "update_dist"), # Nautical Miles ("Satellite ID", "sat_id"), ) class TXT(TalkerSentence): """ Text Transmission """ fields = ( ("Number of Messages", "num_msg"), ("Message Number", "msg_num"), ("Type of Message", "msg_type"), ("Text", "text"), ) class VBW(TalkerSentence, ValidVBWFix): """ Dual Ground/Water Speed """ fields = ( ("Longitudinal Water Speed", "lon_water_spd", Decimal), # Knots ("Transverse Water Speed", "trans_water_spd", Decimal), # Knots ("Water Speed Data Validity", "data_validity_water_spd"), ("Longitudinal Ground Speed", "lon_grnd_spd", Decimal), # Knots ("Transverse Ground Speed", "trans_grnd_spd", Decimal), # Knots ("Ground Speed Data Validity", "data_validity_grnd_spd"), ) class VTG(TalkerSentence): """ Track Made Good and Ground Speed """ fields = ( ("True Track made good", "true_track", float), ("True Track made good symbol", "true_track_sym"), ("Magnetic Track made good", "mag_track", Decimal), ("Magnetic Track symbol", "mag_track_sym"), ("Speed over ground knots", "spd_over_grnd_kts", Decimal), ("Speed over ground symbol", "spd_over_grnd_kts_sym"), ("Speed over ground kmph", "spd_over_grnd_kmph", float), ("Speed over ground kmph symbol", "spd_over_grnd_kmph_sym"), ("FAA mode indicator", "faa_mode"), ) class WCV(TalkerSentence): """ Waypoint Closure Velocity """ fields = ( ("Velocity", "velocity"), ("Velocity Units", "vel_units"), # Knots ("Waypoint ID", "waypoint_id"), ) class WNC(TalkerSentence): """ Distance, Waypoint to Waypoint """ fields = ( ("Distance, Nautical Miles", "dist_nautical_miles"), ("Distance Nautical Miles Unit", "dist_naut_unit"), ("Distance, Kilometers", "dist_km"), ("Distance, Kilometers Unit", "dist_km_unit"), ("Origin Waypoint ID", "waypoint_origin_id"), ("Destination Waypoint ID", "waypoint_dest_id"), ) class WPL(TalkerSentence): """ Waypoint Location """ fields = ( ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Waypoint ID", "waypoint_id"), ) class XTE(TalkerSentence): """ Cross-Track Error, Measured """ fields = ( ("General Warning Flag", "warning_flag"), ("Lock flag (Not Used)", "lock_flag"), ("Cross Track Error Distance", "cross_track_err_dist"), ("Correction Direction (L or R)", "correction_dir"), ("Distance Units", "dist_units"), ) class ZDA(TalkerSentence, DatetimeFix): fields = ( ("Timestamp", "timestamp", timestamp), # hhmmss.ss = UTC ("Day", "day", int), # 01 to 31 ("Month", "month", int), # 01 to 12 ("Year", "year", int), # Year = YYYY ("Local Zone Description", "local_zone", int), # 00 to +/- 13 hours ("Local Zone Minutes Description", "local_zone_minutes", int), # same sign as hours ) @property def datestamp(self): return datetime.date(year=self.year, month=self.month, day=self.day) @property def tzinfo(self): return TZInfo(self.local_zone, self.local_zone_minutes) @property def localdatetime(self): d = datetime.datetime.combine(self.datestamp, self.timestamp) return d.astimezone(self.tzinfo) # Implemented by Janez Stupar for Visionect class RSA(TalkerSentence): """ Rudder Sensor Angle """ fields = ( ("Starboard rudder sensor", "rsa_starboard", Decimal), ("Starboard rudder sensor status", "rsa_starboard_status"), ("Port rudder sensor", "rsa_port", Decimal), ("Port rudder sensor status", "rsa_port_status"), ) class HSC(TalkerSentence): """ Heading Steering Command """ fields = ( ("Heading", "heading_true", Decimal), ("True", "true"), ("Heading Magnetic", "heading_magnetic", Decimal), ("Magnetic", "magnetic"), ) class MWD(TalkerSentence): """ Wind Direction NMEA 0183 standard Wind Direction and Speed, with respect to north. """ fields = ( ("Wind direction true", "direction_true", Decimal), ("True", "true"), ("Wind direction magnetic", "direction_magnetic", Decimal), ("Magnetic", "magnetic"), ("Wind speed knots", "wind_speed_knots", Decimal), ("Knots", "knots"), ("Wind speed meters/second", "wind_speed_meters", Decimal), ("Wind speed", "meters"), ) class MWV(TalkerSentence, ValidStatusFix): """ Wind Speed and Angle NMEA 0183 standard Wind Speed and Angle, in relation to the vessel's bow/centerline. """ fields = ( ("Wind angle", "wind_angle", Decimal), # in relation to vessel's centerline ("Reference", "reference"), # relative (R)/true(T) ("Wind speed", "wind_speed", Decimal), ("Wind speed units", "wind_speed_units"), # K/M/N ("Status", "status"), ) # DBT - Depth below trasducer, depth referenced to the transducer # Used by simrad devices (f.e. EK500) class DBT(TalkerSentence): """ Depth Below Transducer """ fields = ( ("Depth below surface, feet", "depth_feet", Decimal), ("Feet", "unit_feet"), ("Depth below surface, meters", "depth_meters", Decimal), ("Meters", "unit_meters"), ("Depth below surface, fathoms", "depth_fathoms", Decimal), ("fathoms", "unit_fathoms"), ) class HDM(TalkerSentence): """ Heading, Magnetic """ fields = ( ("Heading degrees", "heading", Decimal), ("Magnetic", "magnetic"), ) class MTW(TalkerSentence): """ Water Temperature """ fields = ( ('Water temperature', 'temperature', Decimal), ('Unit of measurement', 'units'), ) class VHW(TalkerSentence): """ Water Speed and Heading """ fields = ( ('Heading true degrees', 'heading_true', Decimal), ('heading true', 'true'), ('Heading Magnetic degrees', 'heading_magnetic', Decimal), ('Magnetic', 'magnetic'), ('Water speed knots', 'water_speed_knots', Decimal), ('Knots', 'knots'), ('Water speed kilometers', 'water_speed_km', Decimal), ('Kilometers', 'kilometers'), ) class VLW(TalkerSentence): """ Distance Traveled through the Water """ fields = ( ('Water trip distance', 'trip_distance', Decimal), ('Trip distance nautical miles', 'trip_distance_miles'), ('Water trip distance since reset', 'trip_distance_reset', Decimal), ('Trip distance nautical miles since reset', 'trip_distance_reset_miles'), ) # --------------------- Implemented by Joachim Bakke (joabakk)---------------- # # ---------------------------------------------------------------------------- # class ROT(TalkerSentence, ValidStatusFix): """ Rate of Turn """ fields = ( ("Rate of turn", "rate_of_turn"), #- indicates bow turn to port ('Status', 'status'), # contains the 'A' or 'B' flag ) class RPM(TalkerSentence, ValidStatusFix): """ Revolutions """ # 1 2 3 4 5 6 # | | | | | | # $--RPM,a,x,x.x,x.x,A*hh # Field Number: # 1) Sourse, S = Shaft, E = Engine # 2) Engine or shaft number # 3) Speed, Revolutions per minute # 4) Propeller pitch, % of maximum, "-" means astern # 5) Status, A means data is valid # 6) Checksum fields = ( ("Source", "source"), #S = Shaft, E = Engine ("Engine or shaft number", "engine_no", int), ("Speed", "speed", float), #RPM ("Propeller pitch", "pitch"), #- means astern ("Status", "status"), #A means valid ) class VPW(TalkerSentence): """ Speed, Measured Parallel to Wind """ fields = ( ("Speed knots", "speed_kn", float),#- means downwind ("Unit knots", "unit_knots"),#N means knots ("Speed m/s", "speed_ms", float), ("Unit m/s", "unit_ms"),#M means m/s ) # VPW - Speed - Measured Parallel to Wind # 1 2 3 4 5 # | | | | | #$--VPW,x.x,N,x.x,M*hh # Field Number: # 1) Speed, "-" means downwind # 2) N = Knots # 3) Speed, "-" means downwind # 4) M = Meters per second # 5) Checksum class VDR(TalkerSentence): fields = ( ("Degrees True", "deg_t", float), ("TRUE", "true"),#T means true ("Degrees Magnetic", "deg_m", float), ("Magnetic", "magnetic"),#M means magnetic ("Speed of Current", "current", float), ("Unit", "unit_kn"), #N means knots ) # VDR - Set and Drift # 1 2 3 4 5 6 7 # | | | | | | | # $--VDR,x.x,T,x.x,M,x.x,N*hh # Field Number: # 1) Degress True # 2) T = True # 3) Degrees Magnetic # 4) M = Magnetic # 5) Knots (speed of current) # 6) N = Knots # 7) Checksum class VWR(TalkerSentence): fields = ( ("Degrees Rel", "deg_r", float), ("Left/Right", "l_r"),#R means right ("Wind speed kn", "wind_speed_kn", float), ("Knots", "unit_knots"),#N means knots ("Wind Speed m/s", "wind_speed_ms", float), ("m/s", "unit_ms"),#M means m/s ("Wind Speed Km/h", "wind_speed_km", float), ("Knots", "unit_km"), #K means Km ) # TODO # getters/setters that normalize units, # apply L/R sign, and sync all fields # when setting the speed #VWR - Relative Wind Speed and Angle # 1 2 3 4 5 6 7 8 9 # | | | | | | | | | # $--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh # Field Number: # 1) Wind direction magnitude in degrees # 2) Wind direction Left/Right of bow # 3) Speed # 4) N = Knots # 5) Speed # 6) M = Meters Per Second # 7) Speed # 8) K = Kilometers Per Hour # 9) Checksum Transducer = namedtuple("Transducer", [ "type", "value", "units", "id", ]) class XDR(TalkerSentence): fields = ( ("Transducer type", "type"), ("Transducer data value", "value"), ("Transducer data units", "units"), ("Transducer ID", "id"), ) @property def num_transducers(self): return len(self.data) // 4 def get_transducer(self, i): return Transducer(*self.data[i*4:i*4+4]) # ------------------------- Implemented by Casper Vertregt ------------------- # # ---------------------------------------------------------------------------- # class OSD(TalkerSentence, ValidStatusFix): """ Own Ship Data """ fields = ( ("True Heading", "heading", Decimal), ("Status", "status"), # A / V ("Vessel Course true degrees", "course", Decimal), ("Course True", "course_true"), # T / R (True / Relative) ("Vessel Speed", "speed", Decimal), ("Speed Reference", "speed_ref"), ("Vessel Set true degrees", "set", Decimal), ("Vessel Drift(speed)", "drift", Decimal), ("Speed Units", "speed_unit"), ) class TLL(TalkerSentence, LatLonFix): """ Target Latitude & Longitude """ fields = ( ("Target Number", "target_number", int), ("Target Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Target Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Target Name", "target_name"), ("Timestamp (UTC)", "timestamp", timestamp), ("Target Status", "target_status"), ("Reference Target", "reference"), ) class TTM(TalkerSentence): """ Tracked Target Message """ fields = ( ("Target Number", "target_number", int), ("Target Distance", "distance", Decimal), ("Bearing from Own Ship", "bearing", Decimal), ("Bearing Reference", "brg_ref"), # T / R (True / Relative) ("Target Speed", "speed", Decimal), ("Target Course over Ground", "cog", Decimal), ("Course Units", "cog_unit"), # T / R (True / Relative) ("Distance of CPA", "dist_cpa", Decimal), ("Time until CPA", "time_cpa", Decimal), ("Distance Units", "dist_unit"), # K / N / S (Kilometers / Knots / Statute miles) ("Target Name", "name"), ("Target Status", "status"), # L / Q / T (Lost from tracking process / Query - in process of acquisition / Tracking at the present time) ("Target Reference", "reference"), # R, null otherwise ("Timestamp (UTC)", "timestamp", timestamp), ("Acquisition Type", "acquisition"), # A / M (Automatic / Manual) ) # ---------------------------------- Not Yet Implemented --------------------- # # ---------------------------------------------------------------------------- # #class FSI(TalkerSentence): # """ Frequency Set Information # """ # fields = ( # ) #class GLC(TalkerSentence): # """ Geographic Position, Loran-C # """ # fields = ( # ) #class GXA(TalkerSentence): # """ TRANSIT Position # """ # fields = ( # ) #class LCD(TalkerSentence): # """ Loran-C Signal Data # """ # fields = ( # ) #class MTA(TalkerSentence): # """ Air Temperature (to be phased out) # """ # fields = ( # ) #class OLN(TalkerSentence): # """ Omega Lane Numbers # """ # fields = ( # ) #class RSD(TalkerSentence): # """ RADAR System Data # """ # fields = ( # ) #class SFI(TalkerSentence): # """ Scanning Frequency Information # """ # fields = ( # ) #class XTR(TalkerSentence): # """ Cross-Track Error, Dead Reckoning # """ # fields = ( # ) #class ZFO(TalkerSentence): # """ UTC & Time from Origin Waypoint # """ # fields = ( # ) #class ZTG(TalkerSentence): # """ UTC & Time to Destination Waypoint # """ # fields = ( # ) # ---------------------------------------------------------------------------- # # -------------------------- Unknown Formats --------------------------------- # # ---------------------------------------------------------------------------- # #class ASD(TalkerSentence): # """ Auto-pilot system data (Unknown format) # """ # fields = ( # ) # ---------------------------------------------------------------------------- # # -------------------------- Obsolete Formats -------------------------------- # # ---------------------------------------------------------------------------- # #class DCN(TalkerSentence): # """ Decca Position (obsolete) # """ # fields = ( # ) # ------------- Implemented by Rocco De Marco (CNR/ISMAR ITALY) -------------- # # ---------------------------------------------------------------------------- # # DTM - NMEA 0183 standard Datum Reference class DTM(TalkerSentence): fields = ( ('Local datum', 'datum'), ('Subdivision datum', 'subd_datum'), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Signed altitude', 'altitude'), ('Datum code', 'datum_code'), ) # Examples: $GPDTM,W84,,0.0,N,0.0,E,0.0,W84*6F # $GPDTM,999,CH95,0.08,N,0.07,E,-47.7,W84*1C # MDA - NMEA 0183 standard Meteorological Composite # Used by Airmar PB 150 weather station class MDA(TalkerSentence): # Example: # $WIMDA,30.2269,I,1.0236,B,17.7,C,,,43.3,,5.0,C,131.5,T,128.6,M,0.8,N,0.4,M*54 fields = ( ('Barometric pressure, inches of mercury', 'b_pressure_inch', Decimal), ('Inches', 'inches'), # I = Inches ('Barometric pressure, bars', 'b_pressure_bar', Decimal), ('Bars', 'bars'), # B = bars ('Air temperature, degrees C', 'air_temp', Decimal), ('Celsius', 'a_celsius'), # C = Celsius ('Water temperature, degrees C', 'water_temp', Decimal), ('Celsius', 'w_celsius'), # C = Celsius ('Relative humidity, percent', 'rel_humidity', Decimal), ('Absolute humidity, percent', 'abs_humidity', Decimal), ('Dew point, degrees C', 'dew_point', Decimal), ('Celsius', 'd_celsius'), # C = Celsius ('Wind direction true', 'direction_true', Decimal), ('True', 'true'), # T = True ('Wind direction magnetic', 'direction_magnetic', Decimal), ('Magnetic', 'magnetic'), # M = Magnetic ('Wind speed knots', 'wind_speed_knots', Decimal), ('Knots', 'knots'), # N = Knots ('Wind speed meters/second', 'wind_speed_meters', Decimal), ('Meters', 'meters'), # M = Meters/second ) # VWT - NMEA 0183 True wind angle in relation to the vessel's heading, and true wind # speed referenced to the water. class VWT(TalkerSentence): fields = ( ('Wind angle relative to the vessel', 'wind_angle_vessel', Decimal), ('Direction, L=Left, R=Right, relative to the vessel head', 'direction'), ('Wind speed knots', 'wind_speed_knots', Decimal), ('Knots', 'knots'), # N = Knots ('Wind speed meters/second', 'wind_speed_meters', Decimal), ('Meters', 'meters'), # M = Meters/second ('Wind speed km/h', 'wind_speed_km', Decimal), ('Km', 'km'), # K = km/h ) # DBS - Depth below surface # Used by simrad devices (f.e. EK500) # Deprecated and replaced by DPT class DBS(TalkerSentence): fields = ( ('Depth below surface, feet', 'depth_feet', Decimal), ('Feets', 'feets'), ('Depth below surface, meters', 'depth_meter', Decimal), ('Meters', 'meters'), ('Depth below surface, fathoms', 'depth_ fathoms', Decimal), ('Fathoms', 'fathoms'), ) # DPT - water depth relative to the transducer and offset of the measuring # transducer # Used by simrad devices (f.e. EK500) class DPT(TalkerSentence): fields = ( ('Water depth, in meters', 'depth', Decimal), ('Offset from the trasducer, in meters', 'offset', Decimal), ('Maximum range scale in use', 'range', Decimal), ) #GBS - GPS Satellite Fault Detection #used by devices such as [MX521] [NV08C-CSM] #description retreived from: "https://www.xj3.nl/download/99/NMEA.txt" class GBS(TalkerSentence): fields = ( ('Timestamp','timestamp', timestamp), ('Expected error in latitude', 'lat_err'), ('Expected error in longitude', 'lon_err'), ('Expected error in altitude', 'alt_err'), ('PRN of most likely failed satellite','sat_prn_num_f'), ('Probability of missed detection for most likely failed satellite','pro_miss', Decimal), ('Estimate of bias in meters on most likely failed satellite','est_bias'), ('Standard deviation of bias estimate','est_bias_dev'), ) # STALK Message - # Used by gadgetpool Seatalk to nmea devices class ALK(TalkerSentence,SeaTalk): fields = ( ("Command", "cmd"), ("Data Byte 1", "data_byte1"), ("Data Byte 2", "data_byte2"), ("Data Byte 3", "data_byte3"), ("Data Byte 4", "data_byte4"), ("Data Byte 5", "data_byte5"), ("Data Byte 6", "data_byte6"), ("Data Byte 7", "data_byte7"), ("Data Byte 8", "data_byte8"), ("Data Byte 9", "data_byte9") ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7357545 pynmea2-1.19.0/pynmea2.egg-info/0000755000175100017510000000000000000000000014405 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161549.0 pynmea2-1.19.0/pynmea2.egg-info/PKG-INFO0000664000175100017510000000214200000000000015503 0ustar00tomtomMetadata-Version: 1.1 Name: pynmea2 Version: 1.19.0 Summary: Python library for the NMEA 0183 protcol Home-page: https://github.com/Knio/pynmea2 Author: Tom Flanagan Author-email: tom@zkpq.ca License: MIT Description: UNKNOWN Keywords: python nmea gps parse parsing nmea0183 0183 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Scientific/Engineering :: GIS Classifier: Topic :: Software Development :: Libraries :: Python Modules ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161549.0 pynmea2-1.19.0/pynmea2.egg-info/SOURCES.txt0000664000175100017510000000200500000000000016270 0ustar00tomtomLICENSE MANIFEST.in README.md setup.py examples/data.log examples/nmea2gpx.py examples/read_file.py examples/read_serial.py pynmea2/__init__.py pynmea2/_version.py pynmea2/nmea.py pynmea2/nmea_file.py pynmea2/nmea_utils.py pynmea2/seatalk_utils.py pynmea2/stream.py pynmea2.egg-info/PKG-INFO pynmea2.egg-info/SOURCES.txt pynmea2.egg-info/dependency_links.txt pynmea2.egg-info/top_level.txt pynmea2/types/__init__.py pynmea2/types/talker.py pynmea2/types/proprietary/__init__.py pynmea2/types/proprietary/ash.py pynmea2/types/proprietary/grm.py pynmea2/types/proprietary/kwd.py pynmea2/types/proprietary/mgn.py pynmea2/types/proprietary/nor.py pynmea2/types/proprietary/rdi.py pynmea2/types/proprietary/srf.py pynmea2/types/proprietary/sxn.py pynmea2/types/proprietary/tnl.py pynmea2/types/proprietary/ubx.py pynmea2/types/proprietary/vtx.py test/test_ash.py test/test_file.py test/test_nor.py test/test_proprietary.py test/test_pynmea.py test/test_rdi.py test/test_stream.py test/test_sxn.py test/test_types.py test/test_utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161549.0 pynmea2-1.19.0/pynmea2.egg-info/dependency_links.txt0000664000175100017510000000000100000000000020455 0ustar00tomtom ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161549.0 pynmea2-1.19.0/pynmea2.egg-info/top_level.txt0000664000175100017510000000001000000000000017130 0ustar00tomtompynmea2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7397547 pynmea2-1.19.0/setup.cfg0000644000175100017510000000004600000000000013161 0ustar00tomtom[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1613862970.0 pynmea2-1.19.0/setup.py0000644000175100017510000000251400000000000013054 0ustar00tomtomfrom setuptools import setup import imp _version = imp.load_source("pynmea2._version", "pynmea2/_version.py") setup( name='pynmea2', version=_version.__version__, author='Tom Flanagan', author_email='tom@zkpq.ca', license='MIT', url='https://github.com/Knio/pynmea2', description='Python library for the NMEA 0183 protcol', packages=['pynmea2','pynmea2.types','pynmea2.types.proprietary'], keywords='python nmea gps parse parsing nmea0183 0183', classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Scientific/Engineering :: GIS', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1674161549.7357545 pynmea2-1.19.0/test/0000755000175100017510000000000000000000000012317 5ustar00tomtom././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/test/test_ash.py0000644000175100017510000000334700000000000014512 0ustar00tomtomimport datetime import pynmea2 def test_ashrltn(): data = '$PASHR,LTN,3*3D' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASHRLTN assert msg.manufacturer == 'ASH' assert msg.subtype == 'LTN' assert msg.latency == 3 assert msg.render() == data def test_ashratt(): data = '$PASHR,130533.620,0.311,T,-80.467,-1.395,0.25,0.066,0.067,0.215,2,3*12' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASHRATT assert msg.data == ['R', '130533.620', '0.311', 'T', '-80.467', '-1.395', '0.25', '0.066', '0.067', '0.215', '2', '3'] assert msg.manufacturer == 'ASH' assert msg.timestamp == datetime.time(13, 5, 33, 620000, tzinfo=datetime.timezone.utc) assert msg.true_heading == 0.311 assert msg.is_true_heading == 'T' assert msg.roll == -80.467 assert msg.pitch == -1.395 assert msg.heave == 0.25 assert msg.roll_accuracy == 0.066 assert msg.pitch_accuracy == 0.067 assert msg.heading_accuracy == 0.215 assert msg.aiding_status == 2 assert msg.imu_status == 3 assert msg.render() == data def test_ashratt_with_2_vs_3_decimal_timestamp(): msg_3 = pynmea2.parse('$PASHR,130533.620,0.311,T,-80.467,-1.395,,0.066,0.067,0.215,2,3*0B') msg_2 = pynmea2.parse('$PASHR,130533.62,0.311,T,-80.467,-1.395,,0.066,0.067,0.215,2,3*3B') assert msg_3.timestamp == msg_2.timestamp def test_ash_undefined(): ''' Test that non-ATT messages still fall back to the generic ASH type ''' data = '$PASHR,XYZ,123' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASH assert msg.manufacturer == 'ASH' # assert msg.sentence_type == 'XYZ' assert msg.data == ['R', 'XYZ', '123'] assert msg.render(checksum=False) == data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/test/test_file.py0000664000175100017510000000314100000000000014650 0ustar00tomtomtry: from StringIO import StringIO except ImportError: from io import StringIO import pynmea2 TEST_DATA = """$GPRMC,181031.576,V,3926.276,N,07739.361,W,99.7,18.30,250915,,E*79 $GPGGA,181032.576,3926.276,N,07739.361,W,0,00,,,M,,M,,*5F $GPGLL,3926.276,N,07739.361,W,181033.576,V*3E $GPRMC,181034.576,V,3949.797,N,07809.854,W,18.8,34.66,250915,,E*75 $GPGGA,181035.576,3949.797,N,07809.854,W,0,00,,,M,,M,,*5A $GPGLL,3949.797,N,07809.854,W,181036.576,V*39 $GPRMC,181037.576,V,4040.018,N,07808.022,W,32.9,16.43,250915,,E*77 $GPGGA,181038.576,4040.018,N,07808.022,W,0,00,,,M,,M,,*58 $GPGLL,4040.018,N,07808.022,W,181039.576,V*39 $GPRMC,181040.576,V,4133.618,N,07725.034,W,96.8,44.47,250915,,E*7F""" def test_file(): nmeafile = pynmea2.NMEAFile(StringIO(TEST_DATA)) nmea_strings = nmeafile.read() assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) del nmeafile with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [_f.readline() for i in range(10)] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [s for s in _f] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [_f.next() for i in range(10)] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) if __name__ == '__main__': test_file() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/test/test_nor.py0000644000175100017510000002030400000000000014525 0ustar00tomtomimport datetime import pynmea2 def test_norbt0(): data = '$PNORBT0,1,040721,131335.3341,23.961,-48.122,-32.76800,10.00000,0.00,0x00000000*48' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT0 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT0' assert msg.beam == 1 assert msg.datestamp == datetime.date(2021, 7, 4) assert msg.timestamp == datetime.time(13, 13, 35, 334100, tzinfo=datetime.timezone.utc) assert msg.dt1 == 23.961 assert msg.dt2 == -48.122 assert msg.bv == -32.76800 assert msg.fom == 10.00000 assert msg.dist == 0.00 assert msg.stat == '0x00000000' assert msg.render() == data def test_norbt4(): data = '$PNORBT4,1.234,-1.234,1.234,23.4,12.34567,12.3*3D' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT4' assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.sound_speed == 1.234 assert msg.dir == 23.4 assert msg.fom == 12.34567 assert msg.dist == 12.3 assert msg.render() == data def test_norbt7(): data = '$PNORBT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*39' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT7 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT7' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.render() == data def test_norbt9(): data = '$PNORBT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*1E' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT9 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT9' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.battery_voltage == 23.4 assert msg.sound_speed == 1567.8 assert msg.pressure == 1.2 assert msg.temp == 12.3 assert msg.stat == '0x000FFFFF' assert msg.render() == data def test_norwt4(): data = '$PNORWT4,1.2345,-1.2345,1.234,23.4,12.34,12.3*1C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT4' assert msg.dt1 == 1.2345 assert msg.dt2 == -1.2345 assert msg.sound_speed == 1.234 assert msg.dir == 23.4 assert msg.fom == 12.34 assert msg.dist == 12.3 assert msg.render() == data def test_norwt7(): data = '$PNORWT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*2C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT7 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT7' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.render() == data def test_norwt9(): data = '$PNORWT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*0B' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT9 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT9' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.battery_voltage == 23.4 assert msg.sound_speed == 1567.8 assert msg.pressure == 1.2 assert msg.temp == 12.3 assert msg.stat == '0x000FFFFF' assert msg.render() == data def test_nori1(): data = '$PNORI1,4,123456,3,30,1.00,5.00,BEAM*5B' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORI1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORI1' assert msg.it == 4 assert msg.sn == 123456 assert msg.nb == 3 assert msg.nc == 30 assert msg.bd == 1.00 assert msg.cs == 5.00 assert msg.cy == 'BEAM' assert msg.render() == data def test_nors1(): data = '$PNORS1,161109,132455,0,34000034,23.9,1500.0,123.4,0.02,45.6,0.02,23.4,0.02,123.456,0.02,24.56*51' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORS1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORS1' assert msg.datestamp == datetime.date(2009, 11, 16) assert msg.timestamp == datetime.time(13, 24, 55, tzinfo=datetime.timezone.utc) assert msg.ec == 0 assert msg.sc == '34000034' assert msg.battery_voltage == 23.9 assert msg.sound_speed == 1500.0 assert msg.heading == 123.4 assert msg.heading_std == 0.02 assert msg.pitch == 45.6 assert msg.pitch_std == 0.02 assert msg.roll == 23.4 assert msg.roll_std == 0.02 assert msg.pressure == 123.456 assert msg.pressure_std == 0.02 assert msg.temp == 24.56 assert msg.render() == data def test_nors4(): data = '$PNORS4,23.6,1530.2,0.0,0.0,0.0,0.000,23.30*66' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORS4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORS4' assert msg.battery_voltage == 23.6 assert msg.sound_speed == 1530.2 assert msg.heading == 0.0 assert msg.pitch == 0.0 assert msg.roll == 0.0 assert msg.pressure == 0.0 assert msg.temp == 23.30 assert msg.render() == data def test_norc1(): data = '$PNORC1,161109,132455,3,11.0,0.332,0.332,0.332,0.332,78.9,78.9,78.9,78.9,78,78,78,78*56' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORC1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORC1' assert msg.datetime == datetime.datetime(2009, 11, 16, 13, 24, 55, tzinfo=datetime.timezone.utc) assert msg.cn == 3 assert msg.cp == 11.0 assert msg.vx == 0.332 assert msg.vy == 0.332 assert msg.vz == 0.332 assert msg.vz2 == 0.332 assert msg.amp1 == 78.9 assert msg.amp2 == 78.9 assert msg.amp3 == 78.9 assert msg.amp4 == 78.9 assert msg.r1 == 78 assert msg.r2 == 78 assert msg.r3 == 78 assert msg.r4 == 78 assert msg.render() == data def test_norc4(): data = '$PNORC4,1.5,1.395,227.1,32,32*7A' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORC4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORC4' assert msg.cp == 1.5 assert msg.sp == 1.395 assert msg.dir == 227.1 assert msg.r == 32 assert msg.amp == 32 assert msg.render() == data def test_norh4(): data = '$PNORH4,161109,143459,0,204C0002*38' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORH4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORH4' assert msg.datestamp == datetime.date(2009, 11, 16) assert msg.timestamp == datetime.time(14, 34, 59, tzinfo=datetime.timezone.utc) assert msg.ec == 0 assert msg.sc == '204C0002' assert msg.render() == data def test_nor_undefined(): ''' Test that non-NOR messages still fall back to the generic NOR type ''' data = '$PNORTT3,XYZ,123' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NOR assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORTT3' assert msg.data == ['XYZ', '123'] assert msg.render(checksum=False) == data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/test/test_proprietary.py0000644000175100017510000002152000000000000016310 0ustar00tomtomimport pynmea2 import datetime def test_proprietary_1(): # A sample proprietary sentence from a LCJ Capteurs # anemometer. data = "$PLCJ,5F01,66FC,AA,9390,6373" msg = pynmea2.parse(data) assert msg.manufacturer == "LCJ" assert msg.data == ['','5F01','66FC','AA','9390','6373'] assert msg.render(checksum=False) == data def test_proprietary_2(): # A sample proprietary sentence from a LCJ Capteurs anemometer. # Note: This sample is the main reason why we can't assume # anything about the content of the proprietary sentences # due to the lack of a comma after the manufacturer ID and # extra comma at the end. data = "$PLCJE81B8,64A0,2800,2162,0E," msg = pynmea2.parse(data) assert msg.manufacturer == 'LCJ' assert msg.data == ['E81B8', '64A0', '2800', '2162', '0E', ''] assert repr(msg) == "" assert msg.render(checksum=False) == data def test_proprietary_3(): # A sample proprietary sentence from a Magellan device # (via ). data = "$PMGNST,02.12,3,T,534,05.0,+03327,00*40" msg = pynmea2.parse(data) assert msg.manufacturer == 'MGN' assert msg.data == ['ST','02.12','3','T','534','05.0','+03327','00'] assert msg.render() == data def test_extra_comma(): # extra comma after name data = "$PTNL,AVR,212604.30,+52.1800,Yaw,,,-0.0807,Roll,12.579,3,1.4,16*21" msg = pynmea2.parse(data) assert msg.manufacturer == 'TNL' assert msg.data == ['', 'AVR','212604.30','+52.1800','Yaw','','','-0.0807','Roll','12.579','3','1.4','16'] assert msg.render() == data def test_proprietary_type(): class ABC(pynmea2.ProprietarySentence): fields = ( ('Empty', '_'), ('First', 'a'), ('Second', 'b'), ) data = '$PABC,1,2*13' msg = pynmea2.parse(data) assert isinstance(msg, ABC) assert msg.manufacturer == 'ABC' assert msg.a == '1' assert msg.b == '2' assert repr(msg) == "" assert str(msg) == data def test_proprietary_with_comma(): # class with no extra comma class TNLDG(pynmea2.tnl.TNL): fields = () # raise Exception(TNL.sentence_types) # raise Exception(pynmea2.ProprietarySentence.sentence_types) data = "$PTNLDG,44.0,33.0,287.0,100,0,4,1,0,,,*3E" msg = pynmea2.parse(data) assert isinstance(msg, TNLDG) assert msg.data == ['DG', '44.0', '33.0', '287.0', '100', '0', '4', '1', '0', '', '', ''] assert str(msg) == data # type with extra comma data = '$PTNL,PJT,NAD83(Conus),CaliforniaZone 4 0404*51' msg = pynmea2.parse(data) assert type(msg) == pynmea2.tnl.TNLPJT assert msg.manufacturer == 'TNL' assert msg.sentence_type == 'PJT' assert msg.coord_name == 'NAD83(Conus)' assert msg.project_name == 'CaliforniaZone 4 0404' assert str(msg) == data def test_srf(): # implemented sentence data = '$PSRF100,0,1200,8,1,1' msg = pynmea2.parse(data) assert type(msg) == pynmea2.srf.SRF100 assert msg.sentence_type == 'SRF100' assert msg.protocol == '0' assert msg.baud == '1200' assert msg.databits == '8' assert msg.stopbits == '1' assert msg.parity == '1' # unimplemented sentence data = '$PSRF999,0,1200,8,1,1' msg = pynmea2.parse(data) assert type(msg) == pynmea2.srf.SRF assert msg.render(checksum=False) == data def test_grm(): data = '$PGRME,15.0,M,45.0,M,25.0,M*1C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.grm.GRME assert msg.sentence_type == 'GRME' assert msg.hpe == 15.0 assert msg.hpe_unit == 'M' assert msg.vpe == 45.0 assert msg.vpe_unit == 'M' assert msg.osepe == 25.0 assert msg.osepe_unit == 'M' assert msg.render() == data def test_tnl(): data = '$PTNL,BPQ,224445.06,021207,3723.09383914,N,12200.32620132,W,EHT-5.923,M,5*60' msg = pynmea2.parse(data) assert type(msg) == pynmea2.tnl.TNLBPQ assert msg.datestamp == datetime.date(2007,12,2) assert msg.latitude == 37.384897319 assert msg.longitude == -122.00543668866666 assert msg.render() == data def test_ubx00(): data = '$PUBX,00,074440.00,4703.74203,N,00736.82976,E,576.991,D3,2.0,2.0,0.091,0.00,-0.032,,0.76,1.05,0.65,14,0,0*70' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX00 assert msg.identifier() == 'PUBX' assert msg.ubx_type == '00' assert msg.timestamp == datetime.time(7, 44, 40, tzinfo=datetime.timezone.utc) assert msg.latitude == 47.06236716666667 assert msg.lat_dir == 'N' assert msg.render() == data def test_ubx03(): data = '$PUBX,03,20,3,e,281,72,36,062,5,e,034,10,23,000,8,U,328,11,44,064,9,-,323,-2,,000,13,-,341,01,,000,16,U,307,45,49,064,18,e,144,18,,000,21,U,150,74,35,037,25,e,134,06,,000,27,U,271,20,52,064,29,U,074,36,36,063,31,U,209,26,37,040,120,e,210,31,,000,126,-,157,33,,000,66,U,036,19,34,015,67,e,090,20,22,000,68,-,136,00,,000,73,e,273,60,47,064,74,U,330,24,44,064,80,U,193,36,36,023*33' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX03 assert msg.num_sv == 20 assert msg.render() == data def test_ubx04(): data = '$PUBX,04,073824.00,131014,113903.99,1814,16,495176,342.504,21*18' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX04 assert msg.date == datetime.date(2014, 10, 13) assert msg.time == datetime.time(7, 38, 24, tzinfo=datetime.timezone.utc) assert msg.clk_bias == 495176 assert msg.render() == data def test_create(): sentence = pynmea2.srf.SRF100('SRF', [ '100', '%d' % 1, '%d' % 9600, '%d' % 7, '%d' % 1, '%d' % 0]) data = sentence.render(checksum=True, dollar=True, newline=False) assert data == '$PSRF100,1,9600,7,1,0*02' def test_unknown_sentence(): data = 'PZZZABC,1,2,3' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ProprietarySentence assert msg.manufacturer == 'ZZZ' assert msg.data == ['ABC', '1', '2', '3'] assert msg.render(checksum=False, dollar=False) == data def test_proprietary_VTX_0002(): # A sample proprietary sentence from a Vectronix device (laser distance) data = "$PVTX,0002,181330,00005.22,M,262.518,T,-01.967,09.358,W*7E" msg = pynmea2.parse(data) assert msg.manufacturer == 'VTX' assert msg.dist == 5.22 assert msg.direction == 262.518 assert msg.va == -1.967 assert msg.render() == data def test_proprietary_VTX_0012(): # A sample proprietary sentence from a Vectronix device (target position) data = "$PVTX,0012,177750,3348.5861,N,10048.5861,W,00045.2,M,038.8,M*22" msg = pynmea2.parse(data) assert msg.manufacturer == 'VTX' assert msg.latitude == 33.80976833333333 assert msg.longitude == -100.80976833333334 assert msg.altitude == 45.2 assert msg.gain == 38.8 assert msg.render() == data def test_proprietary_GRMW(): # A sample proprietary Garmin Waypoint sentence, generated by DIREWOLF data = "$PGRMW,AC7FD-1,,000A,AC7FD local DIGI U=12.5V|T=23.9C*1A" msg = pynmea2.parse(data) assert msg.manufacturer == 'GRM' assert msg.wname == 'AC7FD-1' assert msg.altitude == None assert msg.symbol == '000A' assert msg.comment == 'AC7FD local DIGI U=12.5V|T=23.9C' def test_proprietary_MGNWPL(): # A sample proprietary Magellan Waypoint sentence, generated by DIREWOLF data = "$PMGNWPL,4531.7900,N,12253.4800,W,,M,AC7FD-1,AC7FD local DIGI U=12.5V|T=23.9C,c*46" msg = pynmea2.parse(data) assert msg.manufacturer == 'MGN' assert msg.lat =='4531.7900' assert msg.lat_dir == 'N' assert msg.lon == '12253.4800' assert msg.lon_dir == 'W' assert msg.altitude == None assert msg.altitude_unit == 'M' assert msg.wname == 'AC7FD-1' assert msg.comment == 'AC7FD local DIGI U=12.5V|T=23.9C' assert msg.icon == 'c' assert msg.latitude == 45.529833333333336 assert msg.longitude == -122.89133333333334 def test_KWDWPL(): # A sample proprietary Kenwood Waypoint sentence, generated by DIREWOLF data = "$PKWDWPL,053125,V,4531.7900,N,12253.4800,W,,,200320,,AC7FD-1,/-*10" msg = pynmea2.parse(data) assert msg.manufacturer == "KWD" assert msg.timestamp == datetime.time(5, 31, 25, tzinfo=datetime.timezone.utc) assert msg.status == 'V' assert msg.is_valid == False assert msg.lat == '4531.7900' assert msg.lat_dir == 'N' assert msg.lon == '12253.4800' assert msg.lon_dir == 'W' assert msg.sog == None assert msg.cog == None assert msg.datestamp == datetime.date(2020, 3, 20) assert msg.datetime == datetime.datetime(2020, 3, 20, 5, 31, 25, tzinfo=datetime.timezone.utc) assert msg.altitude == None assert msg.wname == 'AC7FD-1' assert msg.ts == '/-' assert msg.latitude == 45.529833333333336 assert msg.longitude == -122.89133333333334 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161497.0 pynmea2-1.19.0/test/test_pynmea.py0000644000175100017510000001072500000000000015226 0ustar00tomtomimport pytest import pynmea2 data = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D" def test_version(): version = '1.19.0' assert pynmea2.version == version assert pynmea2.__version__ == version def test_sentence(): msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'GGA' assert str(msg) == data def test_checksum(): d = data[:-2] + '00' with pytest.raises(pynmea2.ChecksumError): msg = pynmea2.parse(d) def test_attribute(): msg = pynmea2.parse(data) with pytest.raises(AttributeError): msg.foobar def test_fail(): with pytest.raises(pynmea2.ParseError): pynmea2.parse('FOOBAR') with pytest.raises(pynmea2.SentenceTypeError): pynmea2.parse('$GPABC,1,2,3') def test_mixin(): msg = pynmea2.parse(data) assert msg.latitude == -19.484083333333334 assert msg.longitude == 24.175100000000000 assert msg.latitude_minutes == 29.045000000000073 assert msg.longitude_minutes == 10.506000000000085 assert msg.latitude_seconds == 2.6999999999970896 assert msg.longitude_seconds == 30.360000000000582 def test_missing(): msg = pynmea2.parse("$GPVTG,108.53,T,,M,0.04,N,0.07,K,A*31") assert msg.mag_track == None def test_missing_2(): # $GPGSV,3,1,09,12,28,063,33,14,63,000,32,22,68,150,26,25,40,109,23*7B # $GPGSV,3,2,09,31,42,227,19,32,17,313,20,01,09,316,,11,08,292,*73 # $GPGSV,3,3,09,24,03,046,*47 msg = pynmea2.parse('$GPGSV,3,3,09,24,03,046,*47') assert msg.snr_4 == '' def test_missing_3(): data = '$GPVTG,,T,,M,0.00,N*1B' msg = pynmea2.parse(data) assert None == msg.spd_over_grnd_kmph assert msg.render() == data def test_missing_4(): data = '$GPVTG,,T,,M,0.00,N*1B' msg = pynmea2.parse(data) assert None == msg.spd_over_grnd_kmph assert msg.render() == data def test_dollar(): data = 'GPGSV,3,3,09,24,03,046,*47\r\n' msg = pynmea2.parse(data) assert msg.render(dollar=False, newline=True) == data def test_whitespace(): data = ' GPGSV,3,3,09,24,03,046,*47 \r\n ' msg = pynmea2.parse(data) assert msg.render(dollar=False) == data.strip() def test_nmea_util(): assert pynmea2.nmea_utils.dm_to_sd('0') == 0. assert pynmea2.nmea_utils.dm_to_sd('12108.1') == 121.135 def test_missing_latlon(): data = '$GPGGA,201716.684,,,,,0,00,,,M,0.0,M,,0000*5F' msg = pynmea2.parse(data) print(msg) assert msg.latitude == 0. def test_query(): data = 'CCGPQ,GGA' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.QuerySentence) assert msg.talker == 'CC' assert msg.listener == 'GP' assert msg.sentence_type == 'GGA' msg = pynmea2.QuerySentence('CC', 'GP', 'GGA') assert msg.render() == '$CCGPQ,GGA*2B' def test_slash(): with pytest.raises(pynmea2.nmea.ParseError): msg = pynmea2.parse('$GPGSV,3,3,09,24,03,046,*47\\') def test_timestamp(): t = pynmea2.nmea_utils.timestamp("115919") assert t.hour == 11 assert t.minute == 59 assert t.second == 19 assert t.microsecond == 0 assert pynmea2.nmea_utils.timestamp('115919.1' ).microsecond == 100000 assert pynmea2.nmea_utils.timestamp('115919.12' ).microsecond == 120000 assert pynmea2.nmea_utils.timestamp('115919.123' ).microsecond == 123000 assert pynmea2.nmea_utils.timestamp('115919.1234' ).microsecond == 123400 assert pynmea2.nmea_utils.timestamp('115919.12345' ).microsecond == 123450 assert pynmea2.nmea_utils.timestamp('115919.123456' ).microsecond == 123456 assert pynmea2.nmea_utils.timestamp('115919.1234567').microsecond == 123456 def test_corrupt_message(): # data is corrupt starting here ------------------------------v data = '$GPRMC,172142.00,A,4805.30256324,N,11629.09084774,W,0.D' # fails with strict parsing with pytest.raises(pynmea2.ChecksumError): msg = pynmea2.parse(data, check=True) # lazy parsing succeeds msg = pynmea2.parse(data, check=False) assert isinstance(msg, pynmea2.types.RMC) # corrupt data assert msg.spd_over_grnd == '0.D' # missing data assert msg.true_course == None # renders unchanged assert msg.render(checksum=False) == data # # ^o^ # |\ ship it! # | \ / # /| \ # __/_|___\_ # ~~\________/~~ # ~~~~~~~~~~~~ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/test/test_rdi.py0000664000175100017510000000051500000000000014511 0ustar00tomtomimport pynmea2 def test_rdid(): data = '$PRDID,-1.31,7.81,47.31*68' msg = pynmea2.parse(data) assert type(msg) == pynmea2.rdi.RDID assert msg.manufacturer == 'RDI' assert msg.subtype == 'D' assert msg.pitch == -1.31 assert msg.roll == 7.81 assert msg.heading == 47.31 assert msg.render() == data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549225858.0 pynmea2-1.19.0/test/test_stream.py0000664000175100017510000000354500000000000015234 0ustar00tomtomimport pytest try: from StringIO import StringIO except ImportError: from io import StringIO import pynmea2 DATA = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D\n" def test_stream(): sr = pynmea2.NMEAStreamReader() assert len(list(sr.next(''))) == 0 assert len(list(sr.next(DATA))) == 1 assert len(list(sr.next(DATA))) == 1 sr = pynmea2.NMEAStreamReader() assert len(list(sr.next(DATA))) == 1 assert len(list(sr.next(DATA[:10]))) == 0 assert len(list(sr.next(DATA[10:]))) == 1 sr = pynmea2.NMEAStreamReader() assert list(sr.next()) == [] f = StringIO(DATA * 2) sr = pynmea2.NMEAStreamReader(f) assert len(list(sr.next())) == 1 assert len(list(sr.next())) == 1 assert len(list(sr.next())) == 0 def test_iter(): sr = pynmea2.NMEAStreamReader(StringIO(DATA)) for batch in sr: for msg in batch: assert isinstance(msg, pynmea2.GGA) break break def test_raise_errors(): sr = pynmea2.NMEAStreamReader(errors='raise') assert list(sr.next('foobar')) == [] with pytest.raises(pynmea2.ParseError): assert list(sr.next('foo\n')) def test_yield_errors(): sr = pynmea2.NMEAStreamReader(errors='yield') assert list(sr.next('foobar')) == [] data = list(sr.next('foo\n' + DATA)) assert len(data) == 2 assert isinstance(data[0], pynmea2.ParseError) assert isinstance(data[1], pynmea2.GGA) def test_ignore_errors(): sr = pynmea2.NMEAStreamReader(errors='ignore') assert list(sr.next('foobar')) == [] data = list(sr.next('foo\n' + DATA)) assert len(data) == 1 assert isinstance(data[0], pynmea2.GGA) def test_bad_error_value(): with pytest.raises(ValueError): sr = pynmea2.NMEAStreamReader(errors='bad') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1549148489.0 pynmea2-1.19.0/test/test_sxn.py0000664000175100017510000000123300000000000014541 0ustar00tomtomimport pynmea2 def test_sxn20(): data = '$PSXN,20,0,0,0,0*3B' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.types.sxn.SXN20) assert msg.message_type == 20 assert msg.horiz_qual == 0 assert msg.hgt_qual == 0 assert msg.head_qual == 0 assert msg.rp_qual == 0 assert msg.render() == data def test_sxn23(): data = '$PSXN,23,0.30,-0.97,298.57,0.13*1B' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.types.sxn.SXN23) assert msg.message_type == 23 assert msg.roll == 0.30 assert msg.pitch == -0.97 assert msg.head == 298.57 assert msg.heave == 0.13 assert msg.render() == data ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/test/test_types.py0000644000175100017510000002626000000000000015102 0ustar00tomtomimport pytest import pynmea2 import datetime from decimal import Decimal def test_GGA(): data = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'GGA' assert isinstance(msg, pynmea2.GGA) # Timestamp assert msg.timestamp == datetime.time(18, 43, 53, 70000, tzinfo=datetime.timezone.utc) # Latitude assert msg.lat == '1929.045' # Latitude Direction assert msg.lat_dir == 'S' # Longitude assert msg.lon == '02410.506' # Longitude Direction assert msg.lon_dir == 'E' # GPS Quality Indicator assert msg.gps_qual == 1 # Number of Satellites in use assert msg.num_sats == '04' # Horizontal Dilution of Precision assert msg.horizontal_dil == '2.6' # Antenna Alt above sea level (mean) assert msg.altitude == 100.0 # Units of altitude (meters) assert msg.altitude_units == 'M' # Geoidal Separation assert msg.geo_sep == '-33.9' # Units of Geoidal Separation (meters) assert msg.geo_sep_units == 'M' # Age of Differential GPS Data (secs) assert msg.age_gps_data == '' # Differential Reference Station ID assert msg.ref_station_id == '0000' assert msg.is_valid == True msg.altitude = 200.0 assert str(msg) == "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,200.0,M,-33.9,M,,0000*5E" def test_RTE(): data = "$GPRTE,2,1,c,0,PBRCPK,PBRTO,PTELGR,PPLAND,PYAMBU,PPFAIR,PWARRN,PMORTL,PLISMR*73" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'RTE' assert "2" == msg.num_in_seq assert "1" == msg.sen_num assert "c" == msg.start_type assert "0" == msg.active_route_id assert msg.waypoint_list == [ "PBRCPK", "PBRTO", "PTELGR", "PPLAND", "PYAMBU", "PPFAIR", "PWARRN", "PMORTL", "PLISMR"] msg.waypoint_list = ['ABC', 'DEF'] assert str(msg) == "$GPRTE,2,1,c,0,ABC,DEF*03" def test_R00(): data = "$GPR00,A,B,C*29" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'R00' assert msg.waypoint_list == ['A', 'B', 'C'] msg.waypoint_list = ['ABC', 'DEF'] assert str(msg) == "$GPR00,ABC,DEF*42" def test_MWV(): data = "$IIMWV,271.0,R,000.2,N,A*3B" msg = pynmea2.parse(data) assert msg.talker == 'II' assert msg.sentence_type == 'MWV' # Wind angle in degrees assert msg.wind_angle == Decimal('271.0') # Reference type assert msg.reference == 'R' # Wind speed assert msg.wind_speed == Decimal('0.2') # Wind speed units assert msg.wind_speed_units == 'N' # Device status assert msg.status == 'A' assert msg.render() == data def test_GST(): data = "$GPGST,172814.0,0.006,0.023,0.020,273.6,0.023,0.020,0.031*6A" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.GST) assert msg.timestamp == datetime.time(hour=17, minute=28, second=14, tzinfo=datetime.timezone.utc) assert msg.rms == 0.006 assert msg.std_dev_major == 0.023 assert msg.std_dev_minor == 0.020 assert msg.orientation == 273.6 assert msg.std_dev_latitude == 0.023 assert msg.std_dev_longitude == 0.020 assert msg.std_dev_altitude == 0.031 assert msg.render() == data def test_RMC(): data = '''$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68''' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.RMC) assert msg.timestamp == datetime.time(hour=22, minute=54, second=46, tzinfo=datetime.timezone.utc) assert msg.datestamp == datetime.date(1994, 11, 19) assert msg.latitude == 49.274166666666666 assert msg.longitude == -123.18533333333333 assert msg.datetime == datetime.datetime(1994, 11, 19, 22, 54, 46, tzinfo=datetime.timezone.utc) assert msg.is_valid == True assert msg.render() == data def test_RMC_valid(): '''The RMC mode indicator and navigation status values are optional. Test that when supplied the whole message must be valid. When not supplied only test validation against supplied values. Supplied means that a `,` exists it does NOT mean that a value had to be supplied in the space provided. See https://orolia.com/manuals/VSP/Content/NC_and_SS/Com/Topics/APPENDIX/NMEA_RMCmess.htm for more information about the RMC Message additions. ''' msgs = [ # Original '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,*33', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,*24', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,*72', # RMC Timing Messages '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,S*4C', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,N*51', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,*1F', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,S*5B', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,N*46', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,*08', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,S*0D', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,N*10', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,*5E', # RMC Nav Messags '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,S,S*33', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,S,V*36', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,S,*60', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,N,A*3C', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,N,V*2B', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,N,*7D', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,,A*72', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,,V*65', '$GPRMC,123519.00,A,4807.038,N,01131.000,E,,,230394,,,,*33', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,S,A*36', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,S,V*21', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,S,*77', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,N,A*2B', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,N,V*3C', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,N,*6A', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,,A*65', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,,V*72', '$GPRMC,123519.00,V,4807.038,N,01131.000,E,,,230394,,,,*24', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,S,A*60', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,S,V*77', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,S,*21', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,N,A*7D', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,N,V*6A', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,N,*3C', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,,A*33', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,,V*24', '$GPRMC,123519.00,,4807.038,N,01131.000,E,,,230394,,,,*72', ] # only the first of each section is valid expected = [False] * 39 expected[0] = True expected[3] = True expected[12] = True for i, msg in enumerate(msgs): parsed = pynmea2.parse(msg) assert expected[i] == parsed.is_valid def test_TXT(): data = '$GNTXT,01,01,02,ROM BASE 2.01 (75331) Oct 29 2013 13:28:17*44' msg = pynmea2.parse(data) assert type(msg) == pynmea2.talker.TXT assert msg.text == 'ROM BASE 2.01 (75331) Oct 29 2013 13:28:17' def test_ZDA(): data = '''$GPZDA,010203.05,06,07,2008,-08,30''' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.ZDA) assert msg.timestamp == datetime.time(hour=1, minute=2, second=3, microsecond=50000, tzinfo=datetime.timezone.utc) assert msg.day == 6 assert msg.month == 7 assert msg.year == 2008 assert msg.tzinfo.utcoffset(0) == datetime.timedelta(hours=-8, minutes=30) assert msg.local_zone == -8 assert msg.local_zone_minutes == 30 assert msg.datestamp == datetime.date(2008, 7, 6) assert msg.datetime == datetime.datetime(2008, 7, 6, 1, 2, 3, 50000, tzinfo=datetime.timezone.utc) assert msg.localdatetime == datetime.datetime(2008, 7, 5, 17, 32, 3, 50000, tzinfo=msg.tzinfo) def test_VPW(): data = "$XXVPW,1.2,N,3.4,M" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.VPW) assert msg.talker == 'XX' assert msg.speed_kn == 1.2 assert msg.unit_knots == 'N' assert msg.speed_ms == 3.4 assert msg.unit_ms == 'M' def test_BOD(): data = "XXBOD,045.,T,023.,M,DEST,START" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.BOD) assert msg.talker == 'XX' def test_XDR(): data = "$YXXDR,A,-64.437,M,N,A,054.454,D,E,C,17.09,C,T-N1052*46" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.XDR) assert msg.talker == 'YX' assert msg.type == 'A' assert msg.value == '-64.437' assert msg.units == 'M' assert msg.id == 'N' assert msg.num_transducers == 3 t0 = msg.get_transducer(0) assert t0.type == 'A' assert t0.value == '-64.437' assert t0.units == 'M' assert t0.id == 'N' t1 = msg.get_transducer(1) assert t1.type == 'A' assert t1.value == '054.454' assert t1.units == 'D' assert t1.id == 'E' t2 = msg.get_transducer(2) assert t2.type == 'C' assert t2.value == '17.09' assert t2.units == 'C' assert t2.id == 'T-N1052' def test_GLL(): data = "$GPGLL,4916.45,N,12311.12,W,225444,A,*1D" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render() == data def test_GSA(): data = "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render() == data def test_VBW(): data = "XXVBW,1.2,3.4,A,5.6,7.8,A" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render(checksum=False, dollar=False) == data def test_STALK(): data = "$STALK,9C,C1,2A,E5*4A" msg = pynmea2.parse(data) assert msg.render() == data assert msg.command_name == 'Compass heading and Rudder position' def test_STALK_unidentified_command(): data = "$STALK,AA,C1,2A,E5*30" msg = pynmea2.parse(data) assert msg.render() == data assert msg.command_name == 'Unknown Command' def test_GRS(): data = "$GNGRS,162047.00,1,0.6,0.1,-16.6,-0.8,-0.1,0.5,,,,,,*41" msg = pynmea2.parse(data) assert msg.render() == data assert msg.talker == 'GN' assert msg.sentence_type == 'GRS' assert msg.residuals_mode == 1 assert msg.sv_res_01 == 0.6 assert msg.sv_res_02 == 0.1 assert msg.sv_res_03 == -16.6 assert msg.sv_res_04 == -0.8 assert msg.sv_res_05 == -0.1 assert msg.sv_res_06 == 0.5 assert msg.sv_res_07 == None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1674161423.0 pynmea2-1.19.0/test/test_utils.py0000644000175100017510000000100700000000000015066 0ustar00tomtomimport pytest import pynmea2 import pynmea2.nmea_utils def test_GGA(): data = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D" msg = pynmea2.parse(data) assert msg.latitude == -19.484083333333334 assert msg.longitude == 24.1751 assert msg.is_valid == True def test_latlon(): data = "$GPGGA,161405.680,37.352387,N,121.953086,W,1,10,0.01,-110.342552,M,0.000000,M,,0000,*4E" msg = pynmea2.parse(data) with pytest.raises(ValueError): x = msg.latitude