@ -1,4 +1,5 @@ 
			
		
	
		
			
				
					#!/usr/bin/env python3  
			
		
	
		
			
				
					import  json  
			
		
	
		
			
				
					import  time  
			
		
	
		
			
				
					from  concurrent . futures  import  Future ,  ProcessPoolExecutor  
			
		
	
		
			
				
					from  typing  import  List ,  Optional  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -9,9 +10,10 @@ from collections import defaultdict 
			
		
	
		
			
				
					from  numpy . linalg  import  linalg  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					from  cereal  import  log ,  messaging  
			
		
	
		
			
				
					from  common . params  import  Params ,  put_nonblocking  
			
		
	
		
			
				
					from  laika  import  AstroDog  
			
		
	
		
			
				
					from  laika . constants  import  SECS_IN_HR ,  SECS_IN_MIN  
			
		
	
		
			
				
					from  laika . ephemeris  import  EphemerisType ,  convert_ublox_ephem  
			
		
	
		
			
				
					from  laika . ephemeris  import  Ephemeris ,  Ephemeris Type,  convert_ublox_ephem  
			
		
	
		
			
				
					from  laika . gps_time  import  GPSTime  
			
		
	
		
			
				
					from  laika . helpers  import  ConstellationId  
			
		
	
		
			
				
					from  laika . raw_gnss  import  GNSSMeasurement ,  calc_pos_fix ,  correct_measurements ,  process_measurements ,  read_raw_ublox  
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -22,16 +24,40 @@ import common.transformations.coordinates as coord 
			
		
	
		
			
				
					from  system . swaglog  import  cloudlog  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					MAX_TIME_GAP  =  10  
			
		
	
		
			
				
					EPHEMERIS_CACHE  =  ' LaikadEphemeris '  
			
		
	
		
			
				
					CACHE_VERSION  =  0.1  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					class  Laikad :  
			
		
	
		
			
				
					
  
			
		
	
		
			
				
					  def  __init__ ( self ,  valid_const = ( " GPS " ,  " GLONASS " ) ,  auto_update = False ,  valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT ,  EphemerisType . NAV ) ) :   
			
		
	
		
			
				
					    self . astro_dog  =  AstroDog ( valid_const = valid_const ,  auto_update = auto_update ,  valid_ephem_types = valid_ephem_types )   
			
		
	
		
			
				
					  def  __init__ ( self ,  valid_const = ( " GPS " ,  " GLONASS " ) ,  auto_update = False ,  valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT ,  EphemerisType . NAV ) ,   
			
		
	
		
			
				
					               save_ephemeris = False  ) :   
			
		
	
		
			
				
					    self . astro_dog  =  AstroDog ( valid_const = valid_const ,  auto_update = auto_update ,  valid_ephem_types = valid_ephem_types ,  clear_old_ephemeris = True )   
			
		
	
		
			
				
					    self . gnss_kf  =  GNSSKalman ( GENERATED_DIR )   
			
		
	
		
			
				
					    self . orbit_fetch_executor  =  ProcessPoolExecutor ( )   
			
		
	
		
			
				
					    self . orbit_fetch_future :  Optional [ Future ]  =  None   
			
		
	
		
			
				
					    self . last_fetch_orbits_t  =  None   
			
		
	
		
			
				
					    self . last_cached_t  =  None   
			
		
	
		
			
				
					    self . save_ephemeris  =  save_ephemeris   
			
		
	
		
			
				
					    self . load_cache ( )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  def  load_cache ( self ) :   
			
		
	
		
			
				
					    cache  =  Params ( ) . get ( EPHEMERIS_CACHE )   
			
		
	
		
			
				
					    if  not  cache :   
			
		
	
		
			
				
					      return   
			
		
	
		
			
				
					    try :   
			
		
	
		
			
				
					      cache  =  json . loads ( cache ,  object_hook = deserialize_hook )   
			
		
	
		
			
				
					      self . astro_dog . add_orbits ( cache [ ' orbits ' ] )   
			
		
	
		
			
				
					      self . astro_dog . add_navs ( cache [ ' nav ' ] )   
			
		
	
		
			
				
					      self . last_fetch_orbits_t  =  cache [ ' last_fetch_orbits_t ' ]   
			
		
	
		
			
				
					    except  json . decoder . JSONDecodeError :   
			
		
	
		
			
				
					      cloudlog . exception ( " Error parsing cache " )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  def  cache_ephemeris ( self ,  t :  GPSTime ) :   
			
		
	
		
			
				
					    if  self . save_ephemeris  and  ( self . last_cached_t  is  None  or  t  -  self . last_cached_t  >  SECS_IN_MIN ) :   
			
		
	
		
			
				
					      put_nonblocking ( EPHEMERIS_CACHE ,  json . dumps (   
			
		
	
		
			
				
					        { ' version ' :  CACHE_VERSION ,  ' last_fetch_orbits_t ' :  self . last_fetch_orbits_t ,  ' orbits ' :  self . astro_dog . orbits ,  ' nav ' :  self . astro_dog . nav } ,   
			
		
	
		
			
				
					        cls = CacheSerializer ) )   
			
		
	
		
			
				
					      self . last_cached_t  =  t   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  def  process_ublox_msg ( self ,  ublox_msg ,  ublox_mono_time :  int ,  block = False ) :   
			
		
	
		
			
				
					    if  ublox_msg . which  ==  ' measurementReport ' :   
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -83,7 +109,8 @@ class Laikad: 
			
		
	
		
			
				
					      return  dat   
			
		
	
		
			
				
					    elif  ublox_msg . which  ==  ' ephemeris ' :   
			
		
	
		
			
				
					      ephem  =  convert_ublox_ephem ( ublox_msg . ephemeris )   
			
		
	
		
			
				
					      self . astro_dog . add_navs ( [ ephem ] )   
			
		
	
		
			
				
					      self . astro_dog . add_navs ( { ephem . prn :  [ ephem ] } )   
			
		
	
		
			
				
					      self . cache_ephemeris ( t = ephem . epoch )   
			
		
	
		
			
				
					    # elif ublox_msg.which == 'ionoData':   
			
		
	
		
			
				
					    # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them.   
			
		
	
		
			
				
					
 
			
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
				@ -101,7 +128,7 @@ class Laikad: 
			
		
	
		
			
				
					        cloudlog . error ( " Gnss kalman std too far " )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					      if  len ( pos_fix )  ==  0 :   
			
		
	
		
			
				
					        cloudlog . warning ( " Position fix not available when resetting kalman filter " )   
			
		
	
		
			
				
					        cloudlog . info ( " Position fix not available when resetting kalman filter " )   
			
		
	
		
			
				
					        return   
			
		
	
		
			
				
					      post_est  =  pos_fix [ 0 ] [ : 3 ] . tolist ( )   
			
		
	
		
			
				
					      self . init_gnss_localizer ( post_est )   
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -134,10 +161,11 @@ class Laikad: 
			
		
	
		
			
				
					          self . orbit_fetch_future . result ( )   
			
		
	
		
			
				
					      if  self . orbit_fetch_future . done ( ) :   
			
		
	
		
			
				
					        ret  =  self . orbit_fetch_future . result ( )   
			
		
	
		
			
				
					        self . last_fetch_orbits_t  =  t   
			
		
	
		
			
				
					        if  ret :   
			
		
	
		
			
				
					          self . astro_dog . orbits ,  self . astro_dog . orbit_fetched_times  =  ret   
			
		
	
		
			
				
					          self . cache_ephemeris ( t = t )   
			
		
	
		
			
				
					        self . orbit_fetch_future  =  None   
			
		
	
		
			
				
					        self . last_fetch_orbits_t  =  t   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					def  get_orbit_data ( t :  GPSTime ,  valid_const ,  auto_update ,  valid_ephem_types ) :  
			
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
				@ -193,11 +221,31 @@ def get_bearing_from_gnss(ecef_pos, ecef_vel, vel_std): 
			
		
	
		
			
				
					  return  float ( np . rad2deg ( bearing ) ) ,  float ( bearing_std )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					class  CacheSerializer ( json . JSONEncoder ) :  
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  def  default ( self ,  o ) :   
			
		
	
		
			
				
					    if  isinstance ( o ,  Ephemeris ) :   
			
		
	
		
			
				
					      return  o . to_json ( )   
			
		
	
		
			
				
					    if  isinstance ( o ,  GPSTime ) :   
			
		
	
		
			
				
					      return  o . __dict__   
			
		
	
		
			
				
					    if  isinstance ( o ,  np . ndarray ) :   
			
		
	
		
			
				
					      return  o . tolist ( )   
			
		
	
		
			
				
					    return  json . JSONEncoder . default ( self ,  o )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					def  deserialize_hook ( dct ) :  
			
		
	
		
			
				
					  if  ' ephemeris '  in  dct :   
			
		
	
		
			
				
					    return  Ephemeris . from_json ( dct )   
			
		
	
		
			
				
					  if  ' week '  in  dct :   
			
		
	
		
			
				
					    return  GPSTime ( dct [ ' week ' ] ,  dct [ ' tow ' ] )   
			
		
	
		
			
				
					  return  dct   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					def  main ( ) :  
			
		
	
		
			
				
					  sm  =  messaging . SubMaster ( [ ' ubloxGnss ' ] )   
			
		
	
		
			
				
					  pm  =  messaging . PubMaster ( [ ' gnssMeasurements ' ] )   
			
		
	
		
			
				
					
 
			
		
	
		
			
				
					  laikad  =  Laikad ( )   
			
		
	
		
			
				
					  laikad  =  Laikad ( save_ephemeris = True )   
			
		
	
		
			
				
					  while  True :   
			
		
	
		
			
				
					    sm . update ( )