Package: pyspartn
pyspartnCurrent Status|Installation|Reading|Parsing|Generating|Serializing|Examples|Graphical Client|Author & Licensepyspartnis an original Python 3 parser for the SPARTN © GPS/GNSS protocol. SPARTN is an open-source GPS/GNSSdifferential correction or DGPSprotocol published by u-blox.SPARTN Protocol 2.01(available in the public domain).
© 2021 u-blox AG. All rights reserved.Thepyspartnhomepage is located athttps://github.com/semuconsulting/pyspartn.This is an independent project and we have no affiliation whatsoever with u-blox.FYIThere are companion libraries which handle standard NMEA 0183 ©, UBX © (u-blox) and RTCM3 © GNSS/GPS messages:pyubx2(FYIinstallingpyubx2via pip also installspynmeagpsandpyrtcm)pynmeagpspyrtcmCurrent StatusWORK IN PROGRESS - CURRENTLY IN ALPHA.TheSPARTNReaderclass is fully functional and is capable of parsing individual SPARTN transport-layer messages from a binary data stream containingsolelySPARTN data, with their associated metadata (message type/subtype, payload length, encryption parameters, etc.).TheSPARTNMessageclass implements a provisional decrypt and decode for OCB, HPAC and GAD message types but it has not yet been fully tested (appears to be working OK for GAD, HPAC and most OCB payloads, but some small OCB payloads (nData < 35) are still failing. For the time being, a temporary override has been implemented inspartnmessage.pyto suppress thedecodeflag for those payload types that cannot yet be successfully decoded).There are some additional complexities for messages wheretimeTagtype= 0. Seehttps://portal.u-blox.com/s/question/0D52p0000CimfsOCQQ/spartn-initialization-vector-iv-detailsfor discussion.Sphinx API Documentation in HTML format is available athttps://www.semuconsulting.com/pyspartn.Contributions welcome - please refer toCONTRIBUTING.MD.Bug reportsandFeature requests- please use the templates provided.Installationpyspartnis compatible with Python >=3.8 and is dependent on thecryptographylibrary.NB:If you're installingpyspartnon a 32-bit Linux platform, some additional installation steps may be required - see note¹below.In the following,python3&piprefer to the Python 3 executables. You may need to typepythonorpip3, depending on your particular environment.The recommended way to install the latest version ofpyspartnis withpip:python3-mpipinstall--upgradepyspartnIf required,pyspartncan also be installed into a virtual environment, e.g.:python3-mpipinstall--user--upgradevirtualenv
python3-mvirtualenvenvsourceenv/bin/activate(orenv\Scripts\activateonWindows)(env)python3-mpipinstall--upgradepyspartn
...
deactivate¹On some 32-bit Linux platforms (e.g. Raspberry Pi OS 32), it may be necessary toinstall Rust compiler supportin order to install thecryptographylibrary whichpyspartndepends on to decrypt SPARTN messages (seeDiscussion):Seecryptography install README.Reading (Streaming)class pyspartn.spartnreader.SPARTNReader(stream, **kwargs)You can create aSPARTNReaderobject by calling the constructor with an active stream object.
The stream object can be any data stream which supports aread(n) -> bytesmethod (e.g. File or Serial, with
or without a buffer wrapper).pyspartnimplements an internalSocketStreamclass to allow sockets to be read in the same way as other streams (see example below).Individual SPARTN messages can then be read using theSPARTNReader.read()function, which returns both the raw binary data (as bytes) and the parsed data (as aSPARTNMessage, via theparse()method). The function is thread-safe in so far as the incoming data stream object is thread-safe.SPARTNReaderalso implements an iterator.Example -  Serial input:>>>fromserialimportSerial>>>frompyspartnimportSPARTNReader>>>stream=Serial('/dev/tty.usbmodem14101',9600,timeout=3)>>>spr=SPARTNReader(stream)>>>(raw_data,parsed_data)=spr.read()>>>print(parsed_data)Example - File input (using iterator).>>>frompyspartnimportSPARTNReader>>>stream=open('spartndata.log','rb')>>>spr=SPARTNReader(stream)>>>for(raw_data,parsed_data)inspr:print(parsed_data)...Example - Socket input (using iterator):>>>importsocket>>>frompyspartnimportSPARTNReader>>>stream=socket.socket(socket.AF_INET,socket.SOCK_STREAM):>>>stream.connect(("localhost",50007))>>>spr=SPARTNReader(stream)>>>for(raw_data,parsed_data)inspr:print(parsed_data)ParsingYou can parse individual SPARTN messages using the staticSPARTNReader.parse(data)function, which takes a bytes array containing a binary SPARTN message and returns aSPARTNMessageobject. The optionaldecodekeyword argument signifies whether to decrypt and decode the full payload (default =False). Ifdecodeis set toTrueand the message is encrypted (eaf=1), youmustprovide the following arguments:key- the SPARTN decryption key valid at the time the message was originally created, as provided by your SPARTN service (normally 32 hexadecimal characters).basedate- a nominal datetime or 32-bit gnssTimeTag value, needed to convert an ambiguous 16-bit gnssTimeTag value to its unambiguous 32-bit equivalent. This is used by the decryption routine to determine the cryptographic Initialisation Vector (IV). If you're parsing messages in real time, this can default todatetime.now(). If you're parsing data from an older message stream, you will need to use the datetime the stream was originally created (to the nearest half day), or a 32-bit gnssTimeTag value from the same stream. See examples below.NB:Once instantiated, aSPARTNMMessageobject is immutable.Example - without payload decryption:>>>frompyspartnimportSPARTNReader>>>msg=SPARTNReader.parse(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",decode=False)>>>print(msg)<SPARTN(SPARTN-1X-OCB-GPS,msgType=0,msgSubtype=0,nData=37,eaf=1,crcType=2,frameCrc=2,timeTagtype=0,gnssTimeTag=3970,solutionId=5,solutionProcId=11)>Example - with payload decryption (requires key and, for messages where timeTagtype = 0, a nominal basedate for IV calculation):>>>frompyspartnimportSPARTNReader>>>fromdatetimeimportdatetime>>>msg=SPARTNReader.parse(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',decode=True,key="6b30302427df05b4d98911ebff3a4d95",basedate=datetime(2023,6,27,22,3,0))>>>print(msg)<SPARTN(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=1332,SF033_01=1987,SF034_01=6,SF035_01=2,SF036_01=5,SF037_01=22,SF031_02=33,SF032_02=1332,SF033_02=2033,SF034_02=6,SF035_02=3,SF036_02=5,SF037_02=16,SF031_03=34,SF032_03=1301,SF033_03=1921,SF034_03=2,SF035_03=6,SF036_03=18,SF037_03=10,SF031_04=35,SF032_04=1297,SF033_04=1987,SF034_04=3,SF035_04=3,SF036_04=12,SF037_04=22,SF031_05=36,SF032_05=1448,SF033_05=1768,SF034_05=6,SF035_05=2,SF036_05=5,SF037_05=30,SF031_06=37,SF032_06=1391,SF033_06=1745,SF034_06=4,SF035_06=7,SF036_06=7,SF037_06=10,SF031_07=38,SF032_07=1360,SF033_07=1906,SF034_07=3,SF035_07=2,SF036_07=8,SF037_07=22)>TheSPARTNMessageobject exposes different public attributes depending on its message type or 'identity'. SPARTN data fields are denotedSFnnn- use thedatadesc()helper method to obtain a more user-friendly text description of the data field.>>>frompyspartnimportSPARTNReader,datadesc>>>msg=SPARTNReader.parse(b'\x73\x03\x35\xec\x08\xc7\xd4\x20\x70\x5b\x1f\xc ...\x1e\xbe\x18\x43\x2d\x57\xe7\xa7',decode=True,key="00112233445566778899aabbccddeeff")>>>print(msg)<SPARTN(SPARTN-1X-HPAC-GPS,msgType=1,nData=619,eaf=1,crcType=2,frameCrc=12,msgSubtype=0,timeTagtype=1,gnssTimeTag=419070990,solutionId=5,solutionProcId=11,encryptionId=1,encryptionSeq=63,authInd=1,embAuthLen=0,crc=5760935,SF005=508,SF068=1,SF069=0,SF030=9,SF031_01=0,SF039_01=0,SF040T_01=1,SF040I_01=1,SF041_01=1,SF042_01=2,SF043_01=127,SF044_01=1,SF048_01=213,SF049a_01=257,SF049b_01=253,SF054_01=1,SatBitmaskLen_01=0,SF011_01=70263185,SF055_01_01=6,SF056_01_01=1,SF060_01_01=8944,...SF061b_09_08=8287)>>>>msg.identity'SPARTN-1X-HPAC-GPS'>>>msg.gnssTimeTag419070990>>>msg.SF005508datadesc("SF005")'Solution issue of update (SIOU)'Thepayloadattribute always contains the raw payload as bytes.Generatingclass pyspartn.spartnmessage.SPARTNMessage(**kwargs)You can create anSPARTNMessageobject by calling the constructor with the following keyword arguments:transport as bytesExample:>>>frompyspartnimportSPARTNMessage>>>msg=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")>>>print(msg)<SPARTN(SPARTN-1X-OCB-GPS,msgType=0,msgSubtype=0,nData=37,eaf=1,crcType=2,frameCrc=2,timeTagtype=0,gnssTimeTag=3970,solutionId=5,solutionProcId=11)>SerializingTheSPARTNMessageclass implements aserialize()method to convert aSPARTNMMessageobject to a bytes array suitable for writing to an output stream.e.g. to create and send a1005message type:>>>fromserialimportSerial>>>serialOut=Serial('COM7',38400,timeout=5)>>>frompyspartnimportSPARTNMessage>>>msg=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")>>>print(msg)<SPARTN(SPARTN-1X-OCB-GPS,msgType=0,msgSubtype=0,nData=37,eaf=1,crcType=2,frameCrc=2,timeTagtype=0,gnssTimeTag=3970,solutionId=5,solutionProcId=11)>>>>output=msg.serialize()>>>outputb"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">>>serialOut.write(output)ExamplesThe following examples are available in the /examples folder: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.spartnparser.py- illustrates how to parse SPARTN transport layer data from the binary SPARTN messages output by the example above.gad_plot.py- illustrates how to extract geographic area definitions from a series of SPARTN-GAD-1X messages - the output file from the example above can be used as an input. This example also serves to illustrate how to decrypt SPARTN messages (currently only reliably supported for HPAC and GAD message types).Graphical ClientA python/tkinter graphical GPS client which supports NMEA, UBX, RTCM3 and SPARTN protocols is available at:https://github.com/semuconsulting/PyGPSClientAuthor & License Informationsemuadmin@semuconsulting.compyspartnis maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the library useful, a small donation would be greatly appreciated!