Skip to main content

Quectel QGC protocol parser and generator

Project description

pyqgc

Current Status | Installation | Reading | Parsing | Generating | Serializing | Examples | Extensibility | Troubleshooting | Author & License

pyqgc is an original Python 3 parser for the QGC © protocol. QGC is a proprietary binary protocol implemented on Quectel ™ GNSS receiver modules.

The pyqgc homepage is located at https://github.com/semuconsulting/pyqgc.

This is an independent project and we have no affiliation whatsoever with Quectel.

Current Status

Status Release Build Codecov Release Date Last Commit Contributors Open Issues

The library currently implements the 3 QGC RAW message types defined for the LG580P receiver, but is readily extensible. Refer to QGC_MSGIDS in qgctypes_core.py for the complete dictionary of messages currently supported. QGC protocol information sourced from public domain Quectel Interface Specifications © 2025, Quectel.

Sphinx API Documentation in HTML format is available at https://www.semuconsulting.com/pyqgc/.

Contributions welcome - please refer to CONTRIBUTING.MD. Feel free to discuss any proposed changes beforehand in the Discussion Channel.

Bug reports and Feature requests - please use the templates provided. For general queries and advice, post a message to one of the pyqgc Discussions channels.

No Copilot


Installation

Python version PyPI version PyPI downloads

pyqgc is compatible with Python>=3.10. In the following, python3 & 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).

The recommended way to install the latest version of pyqgc is with pip:

python3 -m pip install --upgrade pyqgc

If required, pyqgc can also be installed into a virtual environment, e.g.:

python3 -m venv env
source env/bin/activate # (or env\Scripts\activate on Windows)
python3 -m pip install --upgrade pyqgc

For Conda users, pyqgc is also available from conda forge:

Anaconda-Server Badge Anaconda-Server Badge

conda install -c conda-forge pyqgc

Reading (Streaming)

class pyqgc.QGCreader.QGCReader(stream, *args, **kwargs)

You can create a QGCReader object by calling the constructor with an active stream object. The stream object can be any viable data stream which supports a read(n) -> bytes method (e.g. File or Serial, with or without a buffer wrapper). pyqgc implements an internal SocketWrapper class to allow sockets to be read in the same way as other streams (see example below).

Individual input QGC messages can then be read using the QGCReader.read() function, which returns both the raw binary data (as bytes) and the parsed data (as a QGCMessage object, via the parse() method). The function is thread-safe in so far as the incoming data stream object is thread-safe. QGCReader also implements an iterator.

The constructor accepts the following optional keyword arguments:

  • quitonerror: ERR_IGNORE (0) = ignore errors, ERR_LOG (1) = log errors and continue (default), ERR_RAISE (2) = (re)raise errors and terminate
  • validate: VALCKSUM (0x01) = validate checksum (default), VALNONE (0x00) = ignore invalid checksum or length
  • parsebitfield: 1 = parse bitfields ('X' type properties) as individual bit flags, where defined (default), 0 = leave bitfields as byte sequences

Example - Serial input:

from serial import Serial
from pyqgc import QGCReader
with Serial('/dev/ttyACM0', 115200, timeout=3) as stream:
  qgr = QGCReader(stream)
  raw_data, parsed_data = qgr.read()
  if parsed_data is not None:
    print(parsed_data)
<QGC(RAW-PPPB2B, msgver=1, reserved1=0, prn=60, pppstatus=0, msgtype=0, reserved2=0, msgdata=b'\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0')>

Example - File input (using iterator):

from pyqgc import QGCReader, QGC_PROTOCOL
with open('QGCdata.bin', 'rb') as stream:
  qgr = QGCReader(stream)
  for raw_data, parsed_data in qg:
    print(parsed_data)
<QGC(RAW-QZSSL6, msgver=1, reserved1=0, prn=195, rsstatus=1, msgtype=1, reserved2=0, msgdata=b'\x1a\xcf\xfc\x1d\xc3\xa9\x7f\x48\xab\x08\x92\x88\xc0\x3a\xa8\x40\x30\x02\x02\x93\xdf\xdf\xf5\xd5\xde\x43\x1c\x00\x00\x02\x04\x80\x04\x70\x00\x00\x00\x00\x82\x40\x7f\x49\xe8\x11\x08\x04\x7f\xed\x40\x0e\x9f\xe2\x01\x8b\x02\xb1\x02\x5c\x00\x5c\x06\x2f\xb1\x9f\xea\xbf\xea\xbf\xf6\x00\x5d\x07\x7b\xf1\x03\xe8\xa7\xf5\x10\x6e\xdf\xd2\x5a\x04\xa2\x3b\xfd\x0d\xfc\x40\x30\x0e\xfc\x5c\x02\x20\x0b\xc8\xbf\x0b\x03\x98\x0d\x60\x5f\x0a\x80\xd4\x03\x98\xbf\x68\xff\xdf\x03\x82\x87\xd5\x4f\xba\x01\x8c\xd7\xf0\x88\x4b\xfe\xd4\x31\xf4\x34\x08\xa0\x3a\x19\xfc\xe7\xf0\x10\x01\x17\x7c\x3a\xfd\xb8\x23\x04\xbf\xb5\x1f\xf2\xff\xe8\x93\xf7\x4c\x01\xc0\x04\x13\xbe\xd9\x00\x2b\xfe\xa2\x77\xde\xd0\x08\x7f\xd8\x4e\xfa\xbd\xff\x70\x03\x09\xdf\x89\xbf\xfa\x00\xca\x5f\xcd\x1f\xb5\xfd\xcd\x2f\xd1\xb0\x0d\xff\x8e\x97\xbf\x17\xf6\x80\x55\xfd\x29\xa0\x4a\x20\x01\xfe\x80\x28\x00\x05\xc0\xa7\xf3\x00\x15\x9c\xcd\xe1\x8b\xc4\xee\x2d\xe8\x5c\xd1\xf1\x28\xf9\xa2\x81\x83\xdf\xeb\x8e\x1b\x5f\xfe\x18\xf7\xd9\x21\x54\x33\x1c\x5c\x10')>
            

Example - Socket input (using iterator):

import socket
from pyqgc import QGCReader, NMEA_PROTOCOL, QGC_PROTOCOL, RTCM3_PROTOCOL
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as stream:
  stream.connect(("localhost", 50007))
  qgr = QGCReader(stream)
  for raw_data, parsed_data in qgr:
    print(parsed_data)
<QGC(RAW-HASE6, msgver=1, reserved1=0, prn=34, hasmode=1, msgtype=1, reserved2=0, page=2, reserved3=0, msgdata=b'\x38\xb2\x00\xe8\x50\xe9\xa0\x5e\x7f\xc6\x0d\x00\x31\xff\x2e\x00\x00\x5b\xfe\x50\x2c\xc0\xe1\x00\x00\x2f\x77\xe0\x0b\x20\xc6\xe5\x3f\x49\x79\xf0\x10\x50\x11\xf8\xcb\xeb\x7f\x31\x04\x67\xd0\x80\xf2\x05\xc0\x0e\x81\xb2\x00\xe8\x50\xe9\xa0\x5e\x7f\xc6\x0d\x00\x31\xff\x2e\x00\x00\x5b\xfe\x50\x2c\xc0\xe1\x00\x00\x2f\x77\xe0\x0b\x20\xc6\xe5\x3f\x49\x79\xf0\x10\x50\x11\xf8\xcb\xeb\x7f\x31\x04\x67\xd0\x80\xf2\x05\xc0\x0e\x81\xc8')>

Parsing

You can parse individual QGC messages using the static QGCReader.parse(data) function, which takes a bytes array containing a binary QGC message and returns a QGCMessage object.

NB: Once instantiated, a QGCMessage object is immutable.

The parse() method accepts the following optional keyword arguments:

  • validate: VALCKSUM (0x01) = validate checksum (default), VALNONE (0x00) = ignore invalid checksum or length
  • parsebitfield: 1 = parse bitfields ('X' type properties) as individual bit flags, where defined (default), 0 = leave bitfields as byte sequences

Example - output message:

from pyqgc import QGCReader
msg = QGCReader.parse(b'\xb5b\x05\x01\x02\x00\x06\x01\x0f\x38')
print(msg)
msg = QGCReader.parse(b'\xb5b\x01\x12$\x000D\n\x18\xfd\xff\xff\xff\xf1\xff\xff\xff\xfc\xff\xff\xff\x10\x00\x00\x00\x0f\x00\x00\x00\x83\xf5\x01\x00A\x00\x00\x00\xf0\xdfz\x00\xd0\xa6')
print(msg)
<QGC(RAW-PPPB2B, msgver=1, reserved1=0, prn=60, pppstatus=0, msgtype=0, reserved2=0, msgdata=b'\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0')>
<QGC(RAW-HASE6, msgver=1, reserved1=0, prn=34, hasmode=1, msgtype=1, reserved2=0, page=2, reserved3=0, msgdata=b'\x38\xb2\x00\xe8\x50\xe9\xa0\x5e\x7f\xc6\x0d\x00\x31\xff\x2e\x00\x00\x5b\xfe\x50\x2c\xc0\xe1\x00\x00\x2f\x77\xe0\x0b\x20\xc6\xe5\x3f\x49\x79\xf0\x10\x50\x11\xf8\xcb\xeb\x7f\x31\x04\x67\xd0\x80\xf2\x05\xc0\x0e\x81\xb2\x00\xe8\x50\xe9\xa0\x5e\x7f\xc6\x0d\x00\x31\xff\x2e\x00\x00\x5b\xfe\x50\x2c\xc0\xe1\x00\x00\x2f\x77\xe0\x0b\x20\xc6\xe5\x3f\x49\x79\xf0\x10\x50\x11\xf8\xcb\xeb\x7f\x31\x04\x67\xd0\x80\xf2\x05\xc0\x0e\x81\xc8')>

The QGCMessage object exposes different public attributes depending on its message type or 'identity', e.g. the RAW-PPPB2B message has the following attributes:

print(msg)
print(msg.identity)
print(msg.pppstatus)
<QGC(RAW-PPPB2B, msgver=1, reserved1=0, prn=60, pppstatus=0, msgtype=0, reserved2=0, msgdata=b'\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0')>
RAW-PPPB2B
0

The payload attribute always contains the raw payload as bytes. Attributes within repeating groups are parsed with a two-digit suffix (svid_01, svid_02, etc.).


Generating

class pyqgc.qgcmessage.QGCMessage(msggrp, msgid, **kwargs)

You can create a QGCMessage object by calling the constructor with the following parameters:

  1. message group (must be a valid group from pyqgc.QGC_MSGIDS)
  2. message id (must be a valid id from pqgc.QGC_MSGIDS)
  3. (optional) a series of keyword parameters representing the message payload
  4. (optional) parsebitfield keyword - 1 = define bitfields as individual bits (default), 0 = define bitfields as byte sequences

The 'message group' and 'message id' parameters must be passed as bytes.

The message payload can be defined via keyword arguments in one of three ways:

  1. A single keyword argument of payload containing the full payload as a sequence of bytes (any other keyword arguments will be ignored). NB the payload keyword argument must be used for message types which have a 'variable by size' repeating group.
  2. One or more keyword arguments corresponding to individual message attributes. Any attributes not explicitly provided as keyword arguments will be set to a nominal value according to their type.
  3. If no keyword arguments are passed, the payload is assumed to be null.

Example - to generate a RAW-PPPB2B command (msggrp 0x0a, msgid 0xb2) from raw binary data:

from pyqgc import QGCMessage
msg = QGCMessage(
    b"\x0a",
    b"\xb2",
    parsebitfield=True,
    msgdata=b"\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0",
)
print(msg)
<QGC(RAW-PPPB2B, msgver=0, reserved1=0, prn=0, pppstatus=0, msgtype=0, reserved2=0, msgdata=b'\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0')>

Serializing

The QGCMessage class implements a serialize() method to convert a QGCMessage object to a bytes array suitable for writing to an output stream.

e.g. to create and send a CFG-MSG message:

print(msg)
output = msg.serialize()
print(output)
<QGC(RAW-PPPB2B, msgver=0, reserved1=0, prn=0, pppstatus=0, msgtype=0, reserved2=0, msgdata=b'\x10\x35\xfc\x49\x04\x40\x01\x3f\x77\x04\x00\x11\x00\x04\x40\x01\x10\x00\x44\x00\x11\x00\x05\x80\x00\x5f\x6b\x84\x00\x11\x00\x07\x7d\x63\x10\x00\x78\x17\x0f\xfd\xd1\x02\x57\x10\x00\x44\x00\x11\x00\x04\x40\x01\x10\x00\x58\x7f\x00\x01\x81\x36\xb0')>
b'QG\n\xb2U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x105\xfcI\x04@\x01?w\x04\x00\x11\x00\x04@\x01\x10\x00D\x00\x11\x00\x05\x80\x00_k\x84\x00\x11\x00\x07}c\x10\x00x\x17\x0f\xfd\xd1\x02W\x10\x00D\x00\x11\x00\x04@\x01\x10\x00X\x7f\x00\x01\x816\xb0\xee\xb1'

Examples

The following command line examples can be found in the \examples folder:

  1. qgcusage.py illustrates basic usage of the QGCMessage and QGCReader classes.

Extensibility

The QGC protocol is principally defined in the modules QGCtypes_*.py as a series of dictionaries. Message payload definitions must conform to the following rules:

1. attribute names must be unique within each message class
2. attribute types must be one of the valid types (S1, U2, X4, etc.)
3. if the attribute is scaled, attribute type is list of [attribute type as string (S1, U2, etc.), scaling factor as float] e.g. {"lat": [I4, 1e-7]}
4. repeating or bitfield groups must be defined as a tuple ('numr', {dict}), where:
   'numr' is either:
     a. an integer representing a fixed number of repeats e.g. 32
     b. a string representing the name of a preceding attribute containing the number of repeats e.g. 'numCh'
     c. an 'X' attribute type ('X1', 'X2', 'X4', etc) representing a group of individual bit flags
     d. 'None' for a 'variable by size' repeating group. Only one such group is permitted per payload and it must be at the end.
   {dict} is the nested dictionary of repeating items or bitfield group

Repeating attribute names are parsed with a two-digit suffix (svid_01, svid_02, etc.). Nested repeating groups are supported.


Troubleshooting

1. UnicodeDecode errors.

  • If reading QGC data from a log file, check that the file.open() procedure is using the rb (read binary) setting e.g. stream = open('QGCdata.log', 'rb').

Author & License Information

semuadmin@semuconsulting.com

License

pyqgc 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...

Sponsor

Freedom for Ukraine

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyqgc-0.1.0.tar.gz (27.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyqgc-0.1.0-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

File details

Details for the file pyqgc-0.1.0.tar.gz.

File metadata

  • Download URL: pyqgc-0.1.0.tar.gz
  • Upload date:
  • Size: 27.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyqgc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6e9387dea06d07bc7b7d23bdddbe4f22f7b7fb823972e573161828537e45d9a6
MD5 5872710f1d20f3f432652582a5739284
BLAKE2b-256 ce73dee27c918b285a3bd03d69690862b25779dcdee8d7a7e45b889eb6552109

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyqgc-0.1.0.tar.gz:

Publisher: deploy.yml on semuconsulting/pyqgc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyqgc-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyqgc-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyqgc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8e8b1958654cc41c439ed47be5438ebb3334c0a32cb54074eafd1de2a9d77211
MD5 dadc34b96452e51e5e38fdbd3d4b2521
BLAKE2b-256 03dffd649879065de9fb870a22f3e653b9b301adb4f749ce88a35733df0fc573

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyqgc-0.1.0-py3-none-any.whl:

Publisher: deploy.yml on semuconsulting/pyqgc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page