{"id":23683924,"url":"https://github.com/semuconsulting/pyspartn","last_synced_at":"2026-03-11T12:26:10.440Z","repository":{"id":65830001,"uuid":"600880812","full_name":"semuconsulting/pyspartn","owner":"semuconsulting","description":"Python library for parsing SPARTN protocol messages. ","archived":false,"fork":false,"pushed_at":"2024-05-20T21:10:20.000Z","size":16958,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-05-20T23:23:10.776Z","etag":null,"topics":["dgps","gnss","gps","rtk","spartn","ublox","ublox-gps"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semuconsulting.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.bib","codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-12T21:32:30.000Z","updated_at":"2024-05-27T11:14:23.512Z","dependencies_parsed_at":null,"dependency_job_id":"697e7329-0b21-4d61-9730-72fadb6048ba","html_url":"https://github.com/semuconsulting/pyspartn","commit_stats":{"total_commits":33,"total_committers":1,"mean_commits":33.0,"dds":0.0,"last_synced_commit":"76d2092c8ae3001fc3acee7ad03948e5dc750603"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpyspartn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpyspartn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpyspartn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpyspartn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semuconsulting","download_url":"https://codeload.github.com/semuconsulting/pyspartn/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231784874,"owners_count":18426283,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dgps","gnss","gps","rtk","spartn","ublox","ublox-gps"],"created_at":"2024-12-29T20:29:59.506Z","updated_at":"2026-03-11T12:26:10.428Z","avatar_url":"https://github.com/semuconsulting.png","language":"Python","funding_links":["https://buymeacoffee.com/semuconsulting"],"categories":[],"sub_categories":[],"readme":"# pyspartn\r\n\r\n[Current Status](#currentstatus) |\r\n[Installation](#installation) |\r\n[Reading](#reading) |\r\n[Parsing](#parsing) |\r\n[Generating](#generating) |\r\n[Serializing](#serializing) |\r\n[Examples](#examples) |\r\n[Troubleshooting](#troubleshooting) |\r\n[Graphical Client](#gui) |\r\n[Author \u0026 License](#author)\r\n\r\n`pyspartn` is an original Python 3 parser for the SPARTN \u0026copy; GPS/GNSS protocol. SPARTN is an open-source GPS/GNSS [differential correction or DGPS](https://en.wikipedia.org/wiki/Differential_GPS) protocol published by u-blox.\r\n\r\nInformation sourced from public domain protocol specification [SPARTN Protocol v2.0.3 (November 2025)](https://www.spartnformat.org/download/) © 2025 u-blox AG. All rights reserved.\r\n\r\nThe `pyspartn` homepage is located at [https://github.com/semuconsulting/pyspartn](https://github.com/semuconsulting/pyspartn).\r\n\r\nThis is an independent project and we have no affiliation whatsoever with u-blox.\r\n\r\n**FYI** There are companion libraries which handle standard NMEA 0183 \u0026copy;, UBX \u0026copy; (u-blox) and RTCM3 \u0026copy; GNSS/GPS messages:\r\n- [pyubx2](http://github.com/semuconsulting/pyubx2)\r\n- [pynmeagps](http://github.com/semuconsulting/pynmeagps)\r\n- [pyrtcm](http://github.com/semuconsulting/pyrtcm)\r\n\r\n## \u003ca name=\"currentstatus\"\u003eCurrent Status\u003c/a\u003e\r\n\r\n![Status](https://img.shields.io/pypi/status/pyspartn)\r\n![Release](https://img.shields.io/github/v/release/semuconsulting/pyspartn?include_prereleases)\r\n![Build](https://img.shields.io/github/actions/workflow/status/semuconsulting/pyspartn/main.yml?branch=main)\r\n![Codecov](https://img.shields.io/codecov/c/github/semuconsulting/pyspartn)\r\n![Release Date](https://img.shields.io/github/release-date-pre/semuconsulting/pyspartn)\r\n![Last Commit](https://img.shields.io/github/last-commit/semuconsulting/pyspartn)\r\n![Contributors](https://img.shields.io/github/contributors/semuconsulting/pyspartn.svg)\r\n![Open Issues](https://img.shields.io/github/issues-raw/semuconsulting/pyspartn)\r\n\r\nThe `SPARTNReader` class is capable of parsing individual SPARTN transport-layer messages from a binary data stream containing *solely* SPARTN data, with their associated metadata (message type/subtype, payload length, encryption parameters, etc.).\r\n\r\nThe `SPARTNMessage` class implements optional decrypt and decode algorithms for individual OCB, HPAC, GAD, BPAC and EAS-DYN message types. Test coverage is currently limited by available SPARTN test data sources.\r\n\r\nSphinx API Documentation in HTML format is available at [https://www.semuconsulting.com/pyspartn](https://www.semuconsulting.com/pyspartn).\r\n\r\nContributions welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pyspartn/blob/master/CONTRIBUTING.md).\r\n\r\n[Bug reports](https://github.com/semuconsulting/pyspartn/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) and [Feature requests](https://github.com/semuconsulting/pyspartn/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) - please use the templates provided. For general queries and advice, please use the [Discussion](https://github.com/semuconsulting/pyspartn/discussions) Forum.\r\n\r\n![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/master/images/nocopilot100.png?raw=true)\r\n\r\n---\r\n## \u003ca name=\"installation\"\u003eInstallation\u003c/a\u003e\r\n\r\n![Python version](https://img.shields.io/pypi/pyversions/pyspartn.svg?style=flat)\r\n[![PyPI version](https://img.shields.io/pypi/v/pyspartn)](https://pypi.org/project/pyspartn/)\r\n[![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/master/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pyspartn)\r\n\r\n`pyspartn` is compatible with Python \u003e= 3.10.\r\n\r\nIn the following, `python3` \u0026 `pip` refer to the Python 3 executables. You may need to substitute `python` for `python3`, depending on your particular environment (*on Windows it's generally `python`*).\r\n\r\nThe recommended way to install the latest version of `pyspartn` is with [pip](http://pypi.python.org/pypi/pip/):\r\n\r\n```shell\r\npython3 -m pip install --upgrade pyspartn\r\n```\r\n\r\nIf required, `pyspartn` can also be installed into a virtual environment, e.g.:\r\n\r\n```shell\r\npython3 -m venv env\r\nsource env/bin/activate # (or env\\Scripts\\activate on Windows)\r\npython3 -m pip install --upgrade pyspartn\r\n```\r\n\r\n`pyspartn` can utilise the Python `cryptography` package to decrypt encrypted SPARTN message payloads*¹ ²*, but as of version 1.0.8 this is *not* installed by default. To enable SPARTN decryption support, install the `cryptography` package separately:\r\n\r\n```shell\r\npython3 -m pip install --upgrade cryptography\r\n```\r\n\r\n*¹* The boolean attribute `pyspartn.HASCRYPTO` can be used to test if decryption support is available at runtime. \r\n\r\n*²* On some 32-bit Linux platforms (e.g. Raspberry Pi OS 32), it may be necessary to [install Rust compiler support](https://www.rust-lang.org/tools/install) in order to install the `cryptography` package which `pyspartn` depends on to decrypt SPARTN message payloads. See [cryptography install README](https://github.com/semuconsulting/pyspartn/blob/main/cryptography_installation/README.md).\r\n\r\nFor [Conda](https://docs.conda.io/en/latest/) users, `pyspartn` is also available from [conda-forge](https://github.com/conda-forge/pyspartn-feedstock):\r\n\r\n[![Anaconda-Server Badge](https://anaconda.org/conda-forge/pyspartn/badges/version.svg)](https://anaconda.org/conda-forge/pyspartn)\r\n[![Anaconda-Server Badge](https://img.shields.io/conda/dn/conda-forge/pyspartn)](https://anaconda.org/conda-forge/pyspartn)\r\n\r\n```shell\r\nconda install -c conda-forge pyspartn\r\n```\r\n\r\n---\r\n## \u003ca name=\"reading\"\u003eReading (Streaming)\u003c/a\u003e\r\n\r\n```\r\nclass pyspartn.spartnreader.SPARTNReader(stream, **kwargs)\r\n```\r\n\r\nYou can create a `SPARTNReader` object by calling the constructor with an active stream object. \r\nThe stream object can be any data stream which supports a `read(n) -\u003e bytes` method (e.g. File or Serial, with \r\nor without a buffer wrapper). `pyspartn` implements an internal `SocketWrapper` class to allow sockets to be read in the same way as other streams.\r\n\r\nIndividual SPARTN messages can then be read using the `SPARTNReader.read()` function, which returns both the raw binary data (as bytes) and the parsed data (as a `SPARTNMessage`, via the `parse()` method). The function is thread-safe in so far as the incoming data stream object is thread-safe. `SPARTNReader` also implements an iterator. See examples below.\r\n\r\nExample -  Serial input:\r\n```python\r\nfrom serial import Serial\r\nfrom pyspartn import SPARTNReader\r\nwith Serial('/dev/tty.usbmodem14101', 38400, timeout=3) as stream:\r\n   spr = SPARTNReader(stream)\r\n   raw_data, parsed_data = spr.read()\r\n   if parsed_data is not None:\r\n      print(parsed_data)\r\n```\r\n\r\nExample - File input (using iterator).\r\n```python\r\nfrom pyspartn import SPARTNReader\r\nwith open('spartndata.log', 'rb') as stream:\r\n   spr = SPARTNReader(stream)\r\n   for raw_data, parsed_data in spr:\r\n      print(parsed_data)\r\n```\r\n\r\nExample - Socket input (using iterator):\r\n```python\r\nimport socket\r\nfrom pyspartn import SPARTNReader\r\nwith socket.socket(socket.AF_INET, socket.SOCK_STREAM) as stream:\r\n   stream.connect((\"localhost\", 50007))\r\n   spr = SPARTNReader(stream)\r\n   for raw_data, parsed_data in spr:\r\n      print(parsed_data)\r\n```\r\n\r\n### Encrypted Payloads\r\n\r\nAt time of writing, most proprietary SPARTN message sources (e.g. Thingstream PointPerfect © MQTT) use encrypted payloads (`eaf=1`). In order to decrypt and decode these payloads, a valid decryption `key` is required. Keys are typically 32-character hexadecimal strings valid for a 4 week period.\r\n\r\nIn addition to the key, the SPARTN decryption algorithm requires a 32-bit `gnssTimeTag` value. The provision of this 32-bit `gnssTimeTag` depends on the incoming data stream:\r\n- Some SPARTN message types (*e.g. HPAC and a few OCB messages*) include the requisite 32-bit `gnssTimeTag` in the message header (denoted by `timeTagtype=1`). Others (*e.g. GAD and most OCB messages*) use an ambiguous 16-bit `gnssTimeTag` value for reasons of brevity (denoted by `timeTagtype=0`). In these circumstances, a nominal 'basedate' must be provided by the user, representing the UTC datetime on which the datastream was originally created to the nearest half day, in order to convert the 16-bit `gnssTimeTag` to an unambiguous 32-bit value.\r\n- If you're parsing data in real time, this basedate can be left at the default `datetime.now(timezone.utc)`.\r\n- If you're parsing historical data, you will need to provide a basedate representing the UTC datetime on which the data stream was originally created, to the nearest half day.\r\n- If a nominal basedate of `TIMEBASE` (`datetime(2010, 1, 1, 0, 0, tzinfo=timezone.utc)`) is provided, `pyspartn.SPARTNReader` can *attempt* to derive the requisite `gnssTimeTag` value from any 32-bit `gnssTimetag` in a preceding message of the same subtype in the same data stream, but *unless and until this eventuality occurs (e.g. unless an HPAC message precedes an OCB message of the same subtype), decryption may fail*. Always set the `quitonerror` argument to `ERRLOG` or `ERRIGNORE` to log or ignore such initial failures.\r\n\r\nThe current decryption key can also be set via environment variable `MQTTKEY`, but bear in mind this will need updating every 4 weeks.\r\n\r\nExample -  Real time serial input with decryption:\r\n```python\r\nfrom serial import Serial\r\nfrom pyspartn import SPARTNReader\r\nwith Serial('/dev/tty.usbmodem14101', 9600, timeout=3) as stream:\r\n   spr = SPARTNReader(stream, decode=1, key=\"930d847b779b126863c8b3b2766ae7cc\")\r\n   for raw_data, parsed_data in spr:\r\n      print(parsed_data)\r\n```\r\n\r\nExample - Historical file input with decryption, using an known basedate:\r\n```python\r\nfrom datetime import datetime, timezone\r\nfrom pyspartn import SPARTNReader\r\n\r\nwith open('spartndata.log', 'rb') as stream:\r\n   spr = SPARTNReader(stream, decode=1, key=\"930d847b779b126863c8b3b2766ae7cc\", basedate=datetime(2023, 4, 18, 20, 48, 29, 977255, tzinfo=timezone.utc))\r\n   for raw_data, parsed_data in spr:\r\n      print(parsed_data)\r\n\r\n```\r\n\r\nExample - Historical file input with decryption, using a nominal TIMEBASE basedate:\r\n```python\r\nfrom datetime import datetime, timezone\r\nfrom pyspartn import SPARTNReader, TIMEBASE, ERRLOG\r\n\r\nwith open('spartndata.log', 'rb') as stream:\r\n   spr = SPARTNReader(stream, decode=1, key=\"930d847b779b126863c8b3b2766ae7cc\", basedate=TIMEBASE, quitonerror=ERRLOG)\r\n   for raw_data, parsed_data in spr:\r\n      print(parsed_data)\r\n\r\n```\r\n```\r\n... (first few messages may fail decryption, until we find a usable 32-bit gnssTimeTag ...)\r\n\"Message type SPARTN-1X-OCB-GPS timetag 33190 not successfully decrypted - check key and basedate\"\r\n\"Message type SPARTN-1X-OCB-GLO timetag 31234 not successfully decrypted - check key and basedate\"\r\n... (but the rest should be decrypted OK ...)\r\n```\r\n\r\n---\r\n## \u003ca name=\"parsing\"\u003eParsing\u003c/a\u003e\r\n\r\nYou can parse individual SPARTN messages using the static `SPARTNReader.parse(data)` function, which takes a bytes array containing a binary SPARTN message and returns a `SPARTNMessage` object. If the message payload is encrypted (`eaf=1`), a decryption `key` and UTC `basedate` must be provided. See examples below.\r\n\r\n**NB:** Once instantiated, a `SPARTNMMessage` object is immutable.\r\n\r\nExample - without payload decryption or decoding:\r\n\r\n```python\r\nfrom pyspartn import SPARTNReader\r\n\r\ntransport = b\"s\\x00\\x12\\xe2\\x00|\\x10[\\x12H\\xf5\\t\\xa0\\xb4+\\x99\\x02\\x15\\xe2\\x05\\x85\\xb7\\x83\\xc5\\xfd\\x0f\\xfe\\xdf\\x18\\xbe\\x7fv \\xc3`\\x82\\x98\\x10\\x07\\xdc\\xeb\\x82\\x7f\\xcf\\xf8\\x9e\\xa3ta\\xad\"\r\nmsg = SPARTNReader.parse(transport, decode=0)\r\nprint(msg)\r\n```\r\n```\r\n\u003cSPARTN(SPARTN-1X-OCB-GPS, msgType=0, nData=37, eaf=1, crcType=2, frameCrc=2, msgSubtype=0, timeTagtype=0, gnssTimeTag=3970, solutionId=5, solutionProcId=11, encryptionId=1, encryptionSeq=9, authInd=1, embAuthLen=0, crc=7627181, )\u003e\r\n```\r\n\r\nExample - with payload decryption and decoding (requires key and, for messages where `timeTagtype=0`, a nominal basedate):\r\n\r\n```python\r\nfrom datetime import datetime, timezone\r\nfrom pyspartn import SPARTNReader\r\n\r\ntransport = b\"\\x73\\x04\\x19\\x62\\x03\\xfa\\x20\\x5b\\x1f\\xc8\\x31\\x0b\\x03\\xd3\\xa4\\xb1\\xdb\\x79\\x21\\xcb\\x5c\\x27\\x12\\xa7\\xa8\\xc2\\x52\\xfd\\x4a\\xfb\\x1a\\x96\\x3b\\x64\\x2a\\x4e\\xcd\\x86\\xbb\\x31\\x7c\\x61\\xde\\xf5\\xdb\\x3d\\xa3\\x2c\\x65\\xd5\\x05\\x9f\\x1c\\xd9\\x96\\x47\\x3b\\xca\\x13\\x5e\\x5e\\x54\\x80\"\r\nmsg = SPARTNReader.parse(\r\n    transport,\r\n    decode=1,\r\n    key=\"6b30302427df05b4d98911ebff3a4d95\",\r\n    basedate=datetime(2023, 6, 27, 22, 3, 0, tzinfo=timezone.utc),\r\n)\r\nprint(msg)\r\n```\r\n```\r\n\u003cSPARTN(SPARTN-1X-GAD, msgType=2, nData=50, eaf=1, crcType=2, frameCrc=2, msgSubtype=0, timeTagtype=0, gnssTimeTag=32580, solutionId=5, solutionProcId=11, encryptionId=1, encryptionSeq=63, authInd=1, embAuthLen=0, crc=6182016, SF005=37, SF068=1, SF069=0, SF030=7, SF031_01=32, SF032_01=43.20000000000002, SF033_01=18.700000000000017, SF034_01=6, SF035_01=2, SF036_01=0.6, SF037_01=2.3000000000000003, SF031_02=33, SF032_02=43.20000000000002, SF033_02=23.30000000000001, SF034_02=6, SF035_02=3, SF036_02=0.6, SF037_02=1.7000000000000002, SF031_03=34, SF032_03=40.099999999999994, SF033_03=12.100000000000023, SF034_03=2, SF035_03=6, SF036_03=1.9000000000000001, SF037_03=1.1, SF031_04=35, SF032_04=39.70000000000002, SF033_04=18.700000000000017, SF034_04=3, SF035_04=3, SF036_04=1.3000000000000003, SF037_04=2.3000000000000003, SF031_05=36, SF032_05=54.80000000000001, SF033_05=-3.1999999999999886, SF034_05=6, SF035_05=2, SF036_05=0.6, SF037_05=3.1, SF031_06=37, SF032_06=49.099999999999994, SF033_06=-5.5, SF034_06=4, SF035_06=7, SF036_06=0.8, SF037_06=1.1, SF031_07=38, SF032_07=46.0, SF033_07=10.600000000000023, SF034_07=3, SF035_07=2, SF036_07=0.9, SF037_07=2.3000000000000003, SF031_08=39, SF032_08=46.0, SF033_08=1.8000000000000114, SF034_08=7, SF035_08=2, SF036_08=0.7000000000000001, SF037_08=2.3000000000000003)\u003e\r\n```\r\n\r\nThe `SPARTNMessage` object exposes different public attributes depending on its message type or 'identity'. SPARTN data fields are denoted `SFnnn` - use the `datadesc()` helper method to obtain a more user-friendly text description of the data field.\r\n\r\n```python\r\nfrom datetime import datetime, timezone\r\nfrom pyspartn import SPARTNReader, datadesc\r\nmsg = SPARTNReader.parse(b\"s\\x02\\xf7\\xeb\\x08\\xd7!\\xef\\x80[\\x17\\x88\\xc2?\\x0f\\x ... \\xc4#fFy\\xb9\\xd5\", decode=True, key=\"930d847b779b126863c8b3b2766ae7cc\", basedate=datetime(2024, 4, 18, 20, 48, 29, 977255, tzinfo=timezone.utc))\r\nprint(msg)\r\nprint(msg.identity)\r\nprint(msg.gnssTimeTag)\r\nprint(datadesc(\"SF005\"), msg.SF005)\r\nprint(datadesc(\"SF061a\"), msg.SF061a_10_05)\r\n```\r\n```\r\n\u003cSPARTN(SPARTN-1X-HPAC-GPS, msgType=1, nData=495, eaf=1, crcType=2, frameCrc=11, msgSubtype=0, timeTagtype=1, gnssTimeTag=451165680, solutionId=5, solutionProcId=11, encryptionId=1, encryptionSeq=30, authInd=1, embAuthLen=0, crc=7977429, SF005=152, SF068=1, SF069=0, SF030=9, SF031_01=0, SF039_01=0, SF040T_01=1, SF040I_01=1, SF041_01=1, SF042_01=1, SF043_01=0.0, SF044_01=1, SF048_01=-0.21199999999999997, SF049a_01=0.0, SF049b_01=0.0010000000000000009, SF054_01=1, SatBitmaskLen_01=0, SF011_01=880836738, SF055_01_01=1, SF056_01_01=1, SF060_01_01=-11.120000000000005, ..., SF061a_10_05=-0.27200000000000557, SF061b_10_05=0.1839999999999975, SF055_10_06=2, SF056_10_06=1, SF060_10_06=7.640000000000043, SF061a_10_06=-1.3840000000000003, SF061b_10_06=-0.7920000000000016)\u003e\r\n'SPARTN-1X-HPAC-GPS'\r\n451165680\r\n('Solution issue of update (SIOU)', 152)\r\n('Large ionosphere coefficient C01', -0.27200000000000557)\r\n```\r\n\r\nAttributes in nested repeating groups are suffixed with a 2-digit index for each nested level e.g. `SF032_06`, `SF061a_10_05`. See [examples below](#iterating) for illustrations of how to iterate through grouped attributes.\r\n\r\nEnumerations for coded values can be found in [spartntables.py](https://github.com/semuconsulting/pyspartn/blob/main/src/pyspartn/spartntables.py).\r\n\r\nThe `payload` attribute always contains the raw payload as bytes.\r\n\r\n#### \u003ca name=\"iterating\"\u003eIterating Through Group Attributes\u003c/a\u003e\r\n\r\nTo iterate through nested grouped attributes, you can use a construct similar to the following (_this example iterates through SF032 Area reference latitude values in a SPARTN-1X-GAD message_):\r\n\r\n```python\r\nvals = []\r\nfor i in range(parsed_data.SF030 + 1):  # attribute or formula representing group size\r\n    vals.append(getattr(parsed_data, f\"SF032_{i+1:02d}\"))\r\nprint(vals)\r\n```\r\n\r\nSee examples `parse_ocb.py`, `parse_hpac.py` and `parse_gad.py` for illustrations of how to convert parsed and decoded OCB, HPAC and GAD payloads into iterable data structures.\r\n\r\n---\r\n## \u003ca name=\"generating\"\u003eGenerating\u003c/a\u003e\r\n\r\n```\r\nclass pyspartn.spartnmessage.SPARTNMessage(**kwargs)\r\n```\r\n\r\nYou can create an `SPARTNMessage` object by calling the constructor with the following keyword arguments:\r\n1. transport as bytes\r\n\r\nExample:\r\n\r\n```python\r\nfrom pyspartn import SPARTNMessage\r\nmsg = SPARTNMessage(transport=b\"s\\x00\\x12\\xe2\\x00|\\x10[\\x12H\\xf5\\t\\xa0\\xb4+\\x99\\x02\\x15\\xe2\\x05\\x85\\xb7\\x83\\xc5\\xfd\\x0f\\xfe\\xdf\\x18\\xbe\\x7fv \\xc3`\\x82\\x98\\x10\\x07\\xdc\\xeb\\x82\\x7f\\xcf\\xf8\\x9e\\xa3ta\\xad\")\r\nprint(msg)\r\n```\r\n```\r\n\u003cSPARTN(SPARTN-1X-OCB-GPS, msgType=0, nData=37, eaf=1, crcType=2, frameCrc=2, msgSubtype=0, timeTagtype=0, gnssTimeTag=3970, solutionId=5, solutionProcId=11, encryptionId=1, encryptionSeq=9, authInd=1, embAuthLen=0, crc=7627181, )\u003e\r\n```\r\n\r\n---\r\n## \u003ca name=\"serializing\"\u003eSerializing\u003c/a\u003e\r\n\r\nThe `SPARTNMessage` class implements a `serialize()` method to convert a `SPARTNMMessage` object to a bytes array suitable for writing to an output stream.\r\n\r\ne.g. to create and send a SPARTN-1X-OCB-GPS message type:\r\n\r\n```python\r\nfrom serial import Serial\r\nserialOut = Serial('/dev/ttyACM1', 38400, timeout=5)\r\nfrom pyspartn import SPARTNMessage\r\nmsg = SPARTNMessage(transport=b\"s\\x00\\x12\\xe2\\x00|\\x10[\\x12H\\xf5\\t\\xa0\\xb4+\\x99\\x02\\x15\\xe2\\x05\\x85\\xb7\\x83\\xc5\\xfd\\x0f\\xfe\\xdf\\x18\\xbe\\x7fv \\xc3`\\x82\\x98\\x10\\x07\\xdc\\xeb\\x82\\x7f\\xcf\\xf8\\x9e\\xa3ta\\xad\")\r\nprint(msg)\r\noutput = msg.serialize()\r\nprint(output)\r\nserialOut.write(output)\r\n```\r\n```\r\n\u003cSPARTN(SPARTN-1X-OCB-GPS, msgType=0, nData=37, eaf=1, crcType=2, frameCrc=2, msgSubtype=0, timeTagtype=0, gnssTimeTag=3970, solutionId=5, solutionProcId=11, encryptionId=1, encryptionSeq=9, authInd=1, embAuthLen=0, crc=7627181, )\u003e\r\nb's\\x00\\x12\\xe2\\x00|\\x10[\\x12H\\xf5\\t\\xa0\\xb4+\\x99\\x02\\x15\\xe2\\x05\\x85\\xb7\\x83\\xc5\\xfd\\x0f\\xfe\\xdf\\x18\\xbe\\x7fv \\xc3`\\x82\\x98\\x10\\x07\\xdc\\xeb\\x82\\x7f\\xcf\\xf8\\x9e\\xa3ta\\xad'\r\n```\r\n\r\n---\r\n## \u003ca name=\"examples\"\u003eExamples\u003c/a\u003e\r\n\r\nThe following examples are available in the /examples folder:\r\n\r\n1. `spartnparser.py` - illustrates how to parse SPARTN transport layer data from a binary SPARTN datastream.\r\n1. `spartn_decrypt.py` - illustrates how to decrypt and decode a binary SPARTN log file (e.g. from the `spartn_mqtt_client.py` or `spartn_ntrip_client.py` examples below).\r\n1. `spartn_mqtt_client.py` - implements a simple SPARTN MQTT client using the [`pygnssutils.GNSSMQTTClient`](https://github.com/semuconsulting/pygnssutils?tab=readme-ov-file#gnssmqttclient) class. **NB**: requires a valid ClientID for a SPARTN MQTT service e.g. u-blox Thingstream PointPerfect MQTT.\r\n1. `spartn_ntrip_client.py` - implements a simple SPARTN NTRIP client using the [`pygnssutils.GNSSNTRIPClient`](https://github.com/semuconsulting/pygnssutils?tab=readme-ov-file#gnssntripclient) class. **NB**: requires a valid user and password for a\r\nSPARTN NTRIP service e.g. u-blox Thingstream PointPerfect NTRIP.\r\n1. `rxmpmp_extract_spartn.py` - ilustrates how to extract individual SPARTN messages from the accumulated UBX-RXM-PMP data output by an NEO-D9S L-band correction receiver.\r\n1. `parse_gad.py` - illustrates how to convert parsed GAD message types into WKT area polygon format for display on a map (see, for example, `gad_plot_map.png`).\r\n1. `parse_hpac.py` and `parse_ocb.py` - illustrate how to convert parsed HPAC and OCB message types into iterable data structures.\r\n\r\n---\r\n## \u003ca name=\"troubleshooting\"\u003eTroubleshooting\u003c/a\u003e\r\n\r\n1. `SPARTNTypeError` or `SPARTNParseError` when parsing encrypted messages with 16-bit gnssTimetags (`timeTagtype=0`), e.g. GAD or some OCB messages:\r\n\r\n   ```\r\n   pyspartn.exceptions.SPARTNTypeError: Error processing attribute 'group' in message type SPARTN-1X-GAD\r\n   ```\r\n\r\n   This is almost certainly due to an invalid decryption key and/or basedate. Remember that keys are only valid for a 4 week period, and basedates are valid for no more than half a day. Note also that different GNSS constellations use different UTC datums e.g. GLONASS timestamps are based on UTC+3. Check with your SPARTN service provider for the latest decryption key(s), and check the original creation date of your SPARTN datasource.\r\n\r\n1. `SSL: CERTIFICATE_VERIFY_FAILED` error when attempting to connect to SPARTN MQTT service using `gnssmqttclient` on MacOS:\r\n\r\n   ```\r\n   [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)\r\n   ```\r\n\r\n   This is because `gnssmqttclient` is unable to locate the RootCA certificate for the MQTT Broker. This can normally be resolved as follows:\r\n   - Install the latest version of certifi: ```python3 -m pip install --upgrade certifi```\r\n   - Run the following command from the terminal (_substituting your Python path and version as required_): ```/Applications/Python\\ 3.12/Install\\ Certificates.command```\r\n\r\n1. Unable to install `crytography` library required by `pyspartn` on 32-bit Linux platforms:\r\n\r\n   ```\r\n   Building wheel for cryptography (PEP 517): started\r\n   Building wheel for cryptography (PEP 517): finished with status 'error'\r\n   ```\r\n\r\n   Refer to [cryptography installation README.md](https://github.com/semuconsulting/pyspartn/blob/main/cryptography_installation/README.md).\r\n\r\n1. Checking for successful decryption. `SPARTNMessage` objects implement a protected attribute `_padding`, which represents the number of redundant bits added to the payload content in order to byte-align the payload with the number of bytes specified in the transport layer payload length attribute `nData`. If the payload has been successfully decrypted and decoded, the value of `_padding` should always be between 0 and 8. Checking `0 \u003c= msg._padding \u003c= 8` provides an informal (_but not necessarily definitive_) check of successful decryption and decoding (see, for example, [spartn_decrypt.py](https://github.com/semuconsulting/pyspartn/blob/main/examples/spartn_decrypt.py)).\r\n\r\n---\r\n## \u003ca name=\"gui\"\u003eGraphical Client\u003c/a\u003e\r\n\r\nA python/tkinter graphical GPS client which supports NMEA, UBX, RTCM3 and SPARTN protocols is available at: \r\n\r\n[https://github.com/semuconsulting/PyGPSClient](https://github.com/semuconsulting/PyGPSClient)\r\n\r\n---\r\n## \u003ca name=\"author\"\u003eAuthor \u0026 License Information\u003c/a\u003e\r\n\r\nsemuadmin@semuconsulting.com\r\n\r\n![License](https://img.shields.io/github/license/semuconsulting/pyspartn.svg)\r\n\r\n`pyspartn` is maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the utility useful, please consider sponsoring the project with the price of a coffee...\r\n\r\n[![Sponsor](https://github.com/semuconsulting/pyubx2/blob/master/images/sponsor.png?raw=true)](https://buymeacoffee.com/semuconsulting)\r\n\r\n[![Freedom for Ukraine](https://github.com/semuadmin/sandpit/blob/main/src/semuadmin_sandpit/resources/ukraine200.jpg?raw=true)](https://u24.gov.ua/)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemuconsulting%2Fpyspartn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemuconsulting%2Fpyspartn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemuconsulting%2Fpyspartn/lists"}