gnss raw measurements through nmea port (#29745)
* gnss measurements through nmea port
* document values
* flags always zero
* fix linter errors
* fix linter errors
old-commit-hash: de97d32fe0
beeps
parent
5dfd03eded
commit
ada436be39
1 changed files with 169 additions and 0 deletions
@ -0,0 +1,169 @@ |
||||
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, "r") 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.sensord.rawgps.rawgpsd import at_cmd |
||||
|
||||
try: |
||||
check_output(["pidof", "rawgpsd"]) |
||||
print("rawgpsd 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() |
Loading…
Reference in new issue