import os
import sys
from dataclasses import dataclass , fields
from subprocess import check_output , CalledProcessError
from time import sleep
from typing import NoReturn
DEBUG = int ( os . environ . get ( " DEBUG " , " 0 " ) )
@dataclass
class GnssClockNmeaPort :
# flags bit mask:
# 0x01 = leap_seconds valid
# 0x02 = time_uncertainty_ns valid
# 0x04 = full_bias_ns valid
# 0x08 = bias_ns valid
# 0x10 = bias_uncertainty_ns valid
# 0x20 = drift_nsps valid
# 0x40 = drift_uncertainty_nsps valid
flags : int
leap_seconds : int
time_ns : int
time_uncertainty_ns : int # 1-sigma
full_bias_ns : int
bias_ns : float
bias_uncertainty_ns : float # 1-sigma
drift_nsps : float
drift_uncertainty_nsps : float # 1-sigma
def __post_init__ ( self ) :
for field in fields ( self ) :
val = getattr ( self , field . name )
setattr ( self , field . name , field . type ( val ) if val else None )
@dataclass
class GnssMeasNmeaPort :
messageCount : int
messageNum : int
svCount : int
# constellation enum:
# 1 = GPS
# 2 = SBAS
# 3 = GLONASS
# 4 = QZSS
# 5 = BEIDOU
# 6 = GALILEO
constellation : int
svId : int
flags : int # always zero
time_offset_ns : int
# state bit mask:
# 0x0001 = CODE LOCK
# 0x0002 = BIT SYNC
# 0x0004 = SUBFRAME SYNC
# 0x0008 = TIME OF WEEK DECODED
# 0x0010 = MSEC AMBIGUOUS
# 0x0020 = SYMBOL SYNC
# 0x0040 = GLONASS STRING SYNC
# 0x0080 = GLONASS TIME OF DAY DECODED
# 0x0100 = BEIDOU D2 BIT SYNC
# 0x0200 = BEIDOU D2 SUBFRAME SYNC
# 0x0400 = GALILEO E1BC CODE LOCK
# 0x0800 = GALILEO E1C 2ND CODE LOCK
# 0x1000 = GALILEO E1B PAGE SYNC
# 0x2000 = GALILEO E1B PAGE SYNC
state : int
time_of_week_ns : int
time_of_week_uncertainty_ns : int # 1-sigma
carrier_to_noise_ratio : float
pseudorange_rate : float
pseudorange_rate_uncertainty : float # 1-sigma
def __post_init__ ( self ) :
for field in fields ( self ) :
val = getattr ( self , field . name )
setattr ( self , field . name , field . type ( val ) if val else None )
def nmea_checksum_ok ( s ) :
checksum = 0
for i , c in enumerate ( s [ 1 : ] ) :
if c == " * " :
if i != len ( s ) - 4 : # should be 3rd to last character
print ( " ERROR: NMEA string does not have checksum delimiter in correct location: " , s )
return False
break
checksum ^ = ord ( c )
else :
print ( " ERROR: NMEA string does not have checksum delimiter: " , s )
return False
return True
def process_nmea_port_messages ( device : str = " /dev/ttyUSB1 " ) - > NoReturn :
while True :
try :
with open ( device ) as nmeaport :
for line in nmeaport :
line = line . strip ( )
if DEBUG :
print ( line )
if not line . startswith ( " $ " ) : # all NMEA messages start with $
continue
if not nmea_checksum_ok ( line ) :
continue
fields = line . split ( " , " )
match fields [ 0 ] :
case " $GNCLK " :
# fields at end are reserved (not used)
gnss_clock = GnssClockNmeaPort ( * fields [ 1 : 10 ] ) # type: ignore[arg-type]
print ( gnss_clock )
case " $GNMEAS " :
# fields at end are reserved (not used)
gnss_meas = GnssMeasNmeaPort ( * fields [ 1 : 14 ] ) # type: ignore[arg-type]
print ( gnss_meas )
except Exception as e :
print ( e )
sleep ( 1 )
def main ( ) - > NoReturn :
from openpilot . common . gpio import gpio_init , gpio_set
from openpilot . system . hardware . tici . pins import GPIO
from openpilot . system . qcomgpsd . qcomgpsd import at_cmd
try :
check_output ( [ " pidof " , " qcomgpsd " ] )
print ( " qcomgpsd is running, please kill openpilot before running this script! (aborted) " )
sys . exit ( 1 )
except CalledProcessError as e :
if e . returncode != 1 : # 1 == no process found (boardd not running)
raise e
print ( " power up antenna ... " )
gpio_init ( GPIO . GNSS_PWR_EN , True )
gpio_set ( GPIO . GNSS_PWR_EN , True )
if b " +QGPS: 0 " not in ( at_cmd ( " AT+QGPS? " ) or b " " ) :
print ( " stop location tracking ... " )
at_cmd ( " AT+QGPSEND " )
if b ' +QGPSCFG: " outport " ,usbnmea ' not in ( at_cmd ( ' AT+QGPSCFG= " outport " ' ) or b " " ) :
print ( " configure outport ... " )
at_cmd ( ' AT+QGPSCFG= " outport " , " usbnmea " ' ) # usbnmea = /dev/ttyUSB1
if b ' +QGPSCFG: " gnssrawdata " ,3,0 ' not in ( at_cmd ( ' AT+QGPSCFG= " gnssrawdata " ' ) or b " " ) :
print ( " configure gnssrawdata ... " )
# AT+QGPSCFG="gnssrawdata",<constellation-mask>,<port>'
# <constellation-mask> values:
# 0x01 = GPS
# 0x02 = GLONASS
# 0x04 = BEIDOU
# 0x08 = GALILEO
# 0x10 = QZSS
# <port> values:
# 0 = NMEA port
# 1 = AT port
at_cmd ( ' AT+QGPSCFG= " gnssrawdata " ,3,0 ' ) # enable all constellations, output data to NMEA port
print ( " rebooting ... " )
at_cmd ( ' AT+CFUN=1,1 ' )
print ( " re-run this script when it is back up " )
sys . exit ( 2 )
print ( " starting location tracking ... " )
at_cmd ( " AT+QGPS=1 " )
process_nmea_port_messages ( )
if __name__ == " __main__ " :
main ( )