Files
github-actions[bot] f8e13417f2 sunnypilot v2026.003.000 release
date: 2026-08-15T14:20:10
master commit: d48cfa730bf21593b684efeaf3fd8940695e1dea
2026-08-15 14:20:16 +08:00

172 lines
5.1 KiB
Python

import os
import sys
from dataclasses import dataclass
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 | None
leap_seconds: int | None
time_ns: int | None
time_uncertainty_ns: int | None # 1-sigma
full_bias_ns: int | None
bias_ns: float | None
bias_uncertainty_ns: float | None # 1-sigma
drift_nsps: float | None
drift_uncertainty_nsps: float | None # 1-sigma
@classmethod
def from_fields(cls, values: list[str]) -> 'GnssClockNmeaPort':
ints = [int(value) if value else None for value in values[:5]]
floats = [float(value) if value else None for value in values[5:9]]
return cls(*ints, *floats)
@dataclass
class GnssMeasNmeaPort:
messageCount: int | None
messageNum: int | None
svCount: int | None
# constellation enum:
# 1 = GPS
# 2 = SBAS
# 3 = GLONASS
# 4 = QZSS
# 5 = BEIDOU
# 6 = GALILEO
constellation: int | None
svId: int | None
flags: int | None # always zero
time_offset_ns: int | None
# 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 | None
time_of_week_ns: int | None
time_of_week_uncertainty_ns: int | None # 1-sigma
carrier_to_noise_ratio: float | None
pseudorange_rate: float | None
pseudorange_rate_uncertainty: float | None # 1-sigma
@classmethod
def from_fields(cls, values: list[str]) -> 'GnssMeasNmeaPort':
ints = [int(value) if value else None for value in values[:10]]
floats = [float(value) if value else None for value in values[10:13]]
return cls(*ints, *floats)
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.from_fields(fields[1:10])
print(gnss_clock)
case "$GNMEAS":
# fields at end are reserved (not used)
gnss_meas = GnssMeasNmeaPort.from_fields(fields[1:14])
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.common.hardware.comma.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 (pandad 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()