commit
						f928161f5f
					
				
				 40 changed files with 396 additions and 178 deletions
			
			
		@ -1 +1 @@ | 
				
			|||||||
fba62008efd13fb578de325f0cdb0a87fe5e28f0 | 
					4f02bcfbf45697c5e6ba0a032797f6b2f37e16d3 | 
				
			||||||
 | 
				
			|||||||
@ -1,122 +0,0 @@ | 
				
			|||||||
#!/usr/bin/env python3 | 
					 | 
				
			||||||
import time | 
					 | 
				
			||||||
import datetime | 
					 | 
				
			||||||
from collections import defaultdict | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from cereal import log | 
					 | 
				
			||||||
import cereal.messaging as messaging | 
					 | 
				
			||||||
from openpilot.common.swaglog import cloudlog | 
					 | 
				
			||||||
from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# https://campar.in.tum.de/twiki/pub/Chair/NaviGpsDemon/nmea.html#RMC | 
					 | 
				
			||||||
""" | 
					 | 
				
			||||||
AT+CGPSGPOS=1 | 
					 | 
				
			||||||
response: '$GNGGA,220212.00,3245.09188,N,11711.76362,W,1,06,24.54,0.0,M,,M,,*77' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AT+CGPSGPOS=2 | 
					 | 
				
			||||||
response: '$GNGSA,A,3,06,17,19,22,,,,,,,,,14.11,8.95,10.91,1*01 | 
					 | 
				
			||||||
$GNGSA,A,3,29,26,,,,,,,,,,,14.11,8.95,10.91,4*03' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AT+CGPSGPOS=3 | 
					 | 
				
			||||||
response: '$GPGSV,3,1,11,06,55,047,22,19,29,053,20,22,19,115,14,05,01,177,,0*68 | 
					 | 
				
			||||||
$GPGSV,3,2,11,11,77,156,23,12,47,322,17,17,08,066,10,20,25,151,,0*6D | 
					 | 
				
			||||||
$GPGSV,3,3,11,24,44,232,,25,16,312,,29,02,260,,0*5D' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AT+CGPSGPOS=4 | 
					 | 
				
			||||||
response: '$GBGSV,1,1,03,26,75,242,20,29,19,049,16,35,,,24,0*7D' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AT+CGPSGPOS=5 | 
					 | 
				
			||||||
response: '$GNRMC,220216.00,A,3245.09531,N,11711.76043,W,,,070324,,,A,V*20' | 
					 | 
				
			||||||
""" | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def sfloat(n: str): | 
					 | 
				
			||||||
  return float(n) if len(n) > 0 else 0 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def checksum(s: str): | 
					 | 
				
			||||||
  ret = 0 | 
					 | 
				
			||||||
  for c in s[1:-3]: | 
					 | 
				
			||||||
    ret ^= ord(c) | 
					 | 
				
			||||||
  return format(ret, '02X') | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main(): | 
					 | 
				
			||||||
  wait_for_modem("AT+CGPS?") | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  cmds = [ | 
					 | 
				
			||||||
    "AT+GPSPORT=1", | 
					 | 
				
			||||||
    "AT+CGPS=1", | 
					 | 
				
			||||||
  ] | 
					 | 
				
			||||||
  for c in cmds: | 
					 | 
				
			||||||
    at_cmd(c) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  nmea = defaultdict(list) | 
					 | 
				
			||||||
  pm = messaging.PubMaster(['gpsLocation']) | 
					 | 
				
			||||||
  while True: | 
					 | 
				
			||||||
    time.sleep(1) | 
					 | 
				
			||||||
    try: | 
					 | 
				
			||||||
      # TODO: read from streaming AT port instead of polling | 
					 | 
				
			||||||
      out = at_cmd("AT+CGPS?") | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if '+CGPS: 1' not in out: | 
					 | 
				
			||||||
        for c in cmds: | 
					 | 
				
			||||||
          at_cmd(c) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      sentences = out.split("'")[1].splitlines() | 
					 | 
				
			||||||
      new = {l.split(',')[0]: l.split(',') for l in sentences if l.startswith('$G')} | 
					 | 
				
			||||||
      nmea.update(new) | 
					 | 
				
			||||||
      if '$GNRMC' not in new: | 
					 | 
				
			||||||
        print(f"no GNRMC:\n{out}\n") | 
					 | 
				
			||||||
        continue | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # validate checksums | 
					 | 
				
			||||||
      for s in nmea.values(): | 
					 | 
				
			||||||
        sent = ','.join(s) | 
					 | 
				
			||||||
        if checksum(sent) != s[-1].split('*')[1]: | 
					 | 
				
			||||||
          cloudlog.error(f"invalid checksum: {repr(sent)}") | 
					 | 
				
			||||||
          continue | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      gnrmc = nmea['$GNRMC'] | 
					 | 
				
			||||||
      #print(gnrmc) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      msg = messaging.new_message('gpsLocation', valid=True) | 
					 | 
				
			||||||
      gps = msg.gpsLocation | 
					 | 
				
			||||||
      gps.latitude = (sfloat(gnrmc[3][:2]) + (sfloat(gnrmc[3][2:]) / 60)) * (1 if gnrmc[4] == "N" else -2) | 
					 | 
				
			||||||
      gps.longitude = (sfloat(gnrmc[5][:3]) + (sfloat(gnrmc[5][3:]) / 60)) * (1 if gnrmc[6] == "E" else -1) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      date = gnrmc[9][:6] | 
					 | 
				
			||||||
      dt = datetime.datetime.strptime(f"{date} {gnrmc[1]}", '%d%m%y %H%M%S.%f') | 
					 | 
				
			||||||
      gps.unixTimestampMillis = dt.timestamp()*1e3 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      gps.hasFix = gnrmc[1] == 'A' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      gps.source = log.GpsLocationData.SensorSource.unicore | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      gps.speed = sfloat(gnrmc[7]) | 
					 | 
				
			||||||
      gps.bearingDeg = sfloat(gnrmc[8]) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if len(nmea['$GNGGA']): | 
					 | 
				
			||||||
        gngga = nmea['$GNGGA'] | 
					 | 
				
			||||||
        if gngga[10] == 'M': | 
					 | 
				
			||||||
          gps.altitude = sfloat(gngga[9]) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if len(nmea['$GNGSA']): | 
					 | 
				
			||||||
        # TODO: this is only for GPS sats | 
					 | 
				
			||||||
        gngsa = nmea['$GNGSA'] | 
					 | 
				
			||||||
        gps.horizontalAccuracy = sfloat(gngsa[4]) | 
					 | 
				
			||||||
        gps.verticalAccuracy = sfloat(gngsa[5]) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # TODO: set these from the module | 
					 | 
				
			||||||
      gps.bearingAccuracyDeg = 5. | 
					 | 
				
			||||||
      gps.speedAccuracy = 3. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # TODO: can we get this from the NMEA sentences? | 
					 | 
				
			||||||
      #gps.vNED = vNED | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      pm.send('gpsLocation', msg) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    except Exception: | 
					 | 
				
			||||||
      cloudlog.exception("gps.issue") | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__": | 
					 | 
				
			||||||
  main() | 
					 | 
				
			||||||
@ -0,0 +1,165 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import time | 
				
			||||||
 | 
					import traceback | 
				
			||||||
 | 
					import serial | 
				
			||||||
 | 
					import datetime | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from collections import defaultdict | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cereal import log | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					from openpilot.common.retry import retry | 
				
			||||||
 | 
					from openpilot.common.swaglog import cloudlog | 
				
			||||||
 | 
					from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sfloat(n: str): | 
				
			||||||
 | 
					  return float(n) if len(n) > 0 else 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def checksum(s: str): | 
				
			||||||
 | 
					  ret = 0 | 
				
			||||||
 | 
					  for c in s[1:-3]: | 
				
			||||||
 | 
					    ret ^= ord(c) | 
				
			||||||
 | 
					  return format(ret, '02X') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Unicore: | 
				
			||||||
 | 
					  def __init__(self): | 
				
			||||||
 | 
					    self.s = serial.Serial('/dev/ttyHS0', 115200) | 
				
			||||||
 | 
					    self.s.timeout = 1 | 
				
			||||||
 | 
					    self.s.writeTimeout = 1 | 
				
			||||||
 | 
					    self.s.newline = b'\r\n' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.s.flush() | 
				
			||||||
 | 
					    self.s.reset_input_buffer() | 
				
			||||||
 | 
					    self.s.reset_output_buffer() | 
				
			||||||
 | 
					    self.s.read(2048) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send(self, cmd): | 
				
			||||||
 | 
					    self.s.write(cmd.encode('utf8') + b'\r') | 
				
			||||||
 | 
					    resp = self.s.read(2048) | 
				
			||||||
 | 
					    print(len(resp), cmd, "\n", resp) | 
				
			||||||
 | 
					    assert b"OK" in resp | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def recv(self): | 
				
			||||||
 | 
					    return self.s.readline() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def build_msg(state): | 
				
			||||||
 | 
					  """ | 
				
			||||||
 | 
					    NMEA sentences: | 
				
			||||||
 | 
					      https://campar.in.tum.de/twiki/pub/Chair/NaviGpsDemon/nmea.html#RMC | 
				
			||||||
 | 
					    NAV messages: | 
				
			||||||
 | 
					      https://www.unicorecomm.com/assets/upload/file/UFirebird_Standard_Positioning_Products_Protocol_Specification_CH.pdf | 
				
			||||||
 | 
					  """ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  msg = messaging.new_message('gpsLocation', valid=True) | 
				
			||||||
 | 
					  gps = msg.gpsLocation | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gnrmc = state['$GNRMC'] | 
				
			||||||
 | 
					  gps.hasFix = gnrmc[1] == 'A' | 
				
			||||||
 | 
					  gps.source = log.GpsLocationData.SensorSource.unicore | 
				
			||||||
 | 
					  gps.latitude = (sfloat(gnrmc[3][:2]) + (sfloat(gnrmc[3][2:]) / 60)) * (1 if gnrmc[4] == "N" else -1) | 
				
			||||||
 | 
					  gps.longitude = (sfloat(gnrmc[5][:3]) + (sfloat(gnrmc[5][3:]) / 60)) * (1 if gnrmc[6] == "E" else -1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    date = gnrmc[9][:6] | 
				
			||||||
 | 
					    dt = datetime.datetime.strptime(f"{date} {gnrmc[1]}", '%d%m%y %H%M%S.%f') | 
				
			||||||
 | 
					    gps.unixTimestampMillis = dt.timestamp()*1e3 | 
				
			||||||
 | 
					  except Exception: | 
				
			||||||
 | 
					    pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gps.bearingDeg = sfloat(gnrmc[8]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(state['$GNGGA']): | 
				
			||||||
 | 
					    gngga = state['$GNGGA'] | 
				
			||||||
 | 
					    if gngga[10] == 'M': | 
				
			||||||
 | 
					      gps.altitude = sfloat(gngga[9]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(state['$GNGSA']): | 
				
			||||||
 | 
					    gngsa = state['$GNGSA'] | 
				
			||||||
 | 
					    gps.horizontalAccuracy = sfloat(gngsa[4]) | 
				
			||||||
 | 
					    gps.verticalAccuracy = sfloat(gngsa[5]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #if len(state['$NAVACC']): | 
				
			||||||
 | 
					  #  # $NAVVEL,264415000,5,3,0.375,0.141,-0.735,-65.450*2A | 
				
			||||||
 | 
					  #  navacc = state['$NAVACC'] | 
				
			||||||
 | 
					  #  gps.horizontalAccuracy = sfloat(navacc[3]) | 
				
			||||||
 | 
					  #  gps.speedAccuracy = sfloat(navacc[4]) | 
				
			||||||
 | 
					  #  gps.bearingAccuracyDeg = sfloat(navacc[5]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(state['$NAVVEL']): | 
				
			||||||
 | 
					    # $NAVVEL,264415000,5,3,0.375,0.141,-0.735,-65.450*2A | 
				
			||||||
 | 
					    navvel = state['$NAVVEL'] | 
				
			||||||
 | 
					    vECEF = [ | 
				
			||||||
 | 
					      sfloat(navvel[4]), | 
				
			||||||
 | 
					      sfloat(navvel[5]), | 
				
			||||||
 | 
					      sfloat(navvel[6]), | 
				
			||||||
 | 
					    ] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lat = np.radians(gps.latitude) | 
				
			||||||
 | 
					    lon = np.radians(gps.longitude) | 
				
			||||||
 | 
					    R = np.array([ | 
				
			||||||
 | 
					      [-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)], | 
				
			||||||
 | 
					      [-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)], | 
				
			||||||
 | 
					      [np.cos(lat), 0, -np.sin(lat)] | 
				
			||||||
 | 
					    ]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vNED = [float(x) for x in R.dot(vECEF)] | 
				
			||||||
 | 
					    gps.vNED = vNED | 
				
			||||||
 | 
					    gps.speed = np.linalg.norm(vNED) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # TODO: set these from the module | 
				
			||||||
 | 
					  gps.bearingAccuracyDeg = 5. | 
				
			||||||
 | 
					  gps.speedAccuracy = 3. | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return msg | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@retry(attempts=10, delay=0.1) | 
				
			||||||
 | 
					def setup(u): | 
				
			||||||
 | 
					  at_cmd('AT+CGPS=0') | 
				
			||||||
 | 
					  at_cmd('AT+CGPS=1') | 
				
			||||||
 | 
					  time.sleep(1.0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # setup NAVXXX outputs | 
				
			||||||
 | 
					  for i in range(4): | 
				
			||||||
 | 
					    u.send(f"$CFGMSG,1,{i},1") | 
				
			||||||
 | 
					  for i in (1, 3): | 
				
			||||||
 | 
					    u.send(f"$CFGMSG,3,{i},1") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # 10Hz NAV outputs | 
				
			||||||
 | 
					  u.send("$CFGNAV,100,100,1000") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(): | 
				
			||||||
 | 
					  wait_for_modem("AT+CGPS?") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  u = Unicore() | 
				
			||||||
 | 
					  setup(u) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state = defaultdict(list) | 
				
			||||||
 | 
					  pm = messaging.PubMaster(['gpsLocation']) | 
				
			||||||
 | 
					  while True: | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      msg = u.recv().decode('utf8').strip() | 
				
			||||||
 | 
					      if "DEBUG" in os.environ: | 
				
			||||||
 | 
					        print(repr(msg)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if len(msg) > 0: | 
				
			||||||
 | 
					        if checksum(msg) != msg.split('*')[1]: | 
				
			||||||
 | 
					          cloudlog.error(f"invalid checksum: {repr(msg)}") | 
				
			||||||
 | 
					          continue | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        k = msg.split(',')[0] | 
				
			||||||
 | 
					        state[k] = msg.split(',') | 
				
			||||||
 | 
					        if '$GNRMC' not in msg: | 
				
			||||||
 | 
					          continue | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pm.send('gpsLocation', build_msg(state)) | 
				
			||||||
 | 
					    except Exception: | 
				
			||||||
 | 
					      traceback.print_exc() | 
				
			||||||
 | 
					      cloudlog.exception("gps.issue") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue