@ -5,6 +5,7 @@ import os
import time
from collections import defaultdict
from concurrent . futures import Future , ProcessPoolExecutor
from datetime import datetime
from enum import IntEnum
from typing import List , Optional
@ -13,7 +14,7 @@ import numpy as np
from cereal import log , messaging
from common . params import Params , put_nonblocking
from laika import AstroDog
from laika . constants import SECS_IN_MIN
from laika . constants import SECS_IN_HR , SECS_IN_ MIN
from laika . ephemeris import Ephemeris , EphemerisType , convert_ublox_ephem
from laika . gps_time import GPSTime
from laika . helpers import ConstellationId
@ -30,7 +31,8 @@ CACHE_VERSION = 0.1
class Laikad :
def __init__ ( self , valid_const = ( " GPS " , " GLONASS " ) , auto_fetch_orbits = True , auto_update = False , valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT , EphemerisType . NAV ) ,
def __init__ ( self , valid_const = ( " GPS " , " GLONASS " ) , auto_fetch_orbits = True , auto_update = False ,
valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT , EphemerisType . NAV ) ,
save_ephemeris = False ) :
"""
valid_const : GNSS constellation which can be used
@ -47,6 +49,7 @@ class Laikad:
self . orbit_fetch_future : Optional [ Future ] = None
self . last_fetch_orbits_t = None
self . got_first_ublox_msg = False
self . last_cached_t = None
self . save_ephemeris = save_ephemeris
self . load_cache ( )
@ -72,7 +75,8 @@ class Laikad:
except json . decoder . JSONDecodeError :
cloudlog . exception ( " Error parsing cache " )
timestamp = self . last_fetch_orbits_t . as_datetime ( ) if self . last_fetch_orbits_t is not None else ' Nan '
cloudlog . debug ( f " Loaded nav and orbits cache with timestamp: { timestamp } . Unique orbit and nav sats: { list ( cache [ ' orbits ' ] . keys ( ) ) } { list ( cache [ ' nav ' ] . keys ( ) ) } " +
cloudlog . debug (
f " Loaded nav and orbits cache with timestamp: { timestamp } . Unique orbit and nav sats: { list ( cache [ ' orbits ' ] . keys ( ) ) } { list ( cache [ ' nav ' ] . keys ( ) ) } " +
f " Total: { sum ( [ len ( v ) for v in cache [ ' orbits ' ] ] ) } and { sum ( [ len ( v ) for v in cache [ ' nav ' ] ] ) } " )
def cache_ephemeris ( self , t : GPSTime ) :
@ -98,9 +102,10 @@ class Laikad:
t = ublox_mono_time * 1e-9
report = ublox_msg . measurementReport
if report . gpsWeek > 0 :
self . got_first_ublox_msg = True
latest_msg_t = GPSTime ( report . gpsWeek , report . rcvTow )
if self . auto_fetch_orbits :
self . fetch_orbits ( latest_msg_t + SECS_IN_MIN , block )
self . fetch_orbits ( latest_msg_t , block )
new_meas = read_raw_ublox ( report )
# Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites
@ -174,23 +179,25 @@ class Laikad:
self . gnss_kf . init_state ( x_initial , covs_diag = p_initial_diag )
def fetch_orbits ( self , t : GPSTime , block ) :
if t not in self . astro_dog . orbit_fetched_times and ( self . last_fetch_orbits_t is None or t - self . last_fetch_orbits_t > SECS_IN_MIN ) :
# Download new orbits if 1 hour of orbits data left
if t + SECS_IN_HR not in self . astro_dog . orbit_fetched_times and ( self . last_fetch_orbits_t is None or abs ( t - self . last_fetch_orbits_t ) > SECS_IN_MIN ) :
astro_dog_vars = self . astro_dog . valid_const , self . astro_dog . auto_update , self . astro_dog . valid_ephem_types
ret = None
if block :
if block : # Used for testing purposes
ret = get_orbit_data ( t , * astro_dog_vars )
elif self . orbit_fetch_future is None :
self . orbit_fetch_executor = ProcessPoolExecutor ( max_workers = 1 )
self . orbit_fetch_future = self . orbit_fetch_executor . submit ( get_orbit_data , t , * astro_dog_vars )
elif self . orbit_fetch_future . done ( ) :
self . last_fetch_orbits_t = t
ret = self . orbit_fetch_future . result ( )
self . orbit_fetch_executor = self . orbit_fetch_future = None
if ret is not None :
self . astro_dog . orbits , self . astro_dog . orbit_fetched_times = ret
if ret [ 0 ] is None :
self . last_fetch_orbits_t = ret [ 2 ]
else :
self . astro_dog . orbits , self . astro_dog . orbit_fetched_times , self . last_fetch_orbits_t = ret
self . cache_ephemeris ( t = t )
@ -201,9 +208,10 @@ def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types):
try :
astro_dog . get_orbit_data ( t , only_predictions = True )
cloudlog . info ( f " Done parsing orbits. Took { time . monotonic ( ) - start_time : .1f } s " )
return astro_dog . orbits , astro_dog . orbit_fetched_times
return astro_dog . orbits , astro_dog . orbit_fetched_times , t
except ( RuntimeError , ValueError , IOError ) as e :
cloudlog . warning ( f " No orbit data found or parsing failure: { e } " )
return None , None , t
def create_measurement_msg ( meas : GNSSMeasurement ) :
@ -284,7 +292,7 @@ class EphemerisSourceType(IntEnum):
def main ( sm = None , pm = None ) :
if sm is None :
sm = messaging . SubMaster ( [ ' ubloxGnss ' ] )
sm = messaging . SubMaster ( [ ' ubloxGnss ' , ' clocks ' ] )
if pm is None :
pm = messaging . PubMaster ( [ ' gnssMeasurements ' ] )
@ -299,6 +307,11 @@ def main(sm=None, pm=None):
msg = laikad . process_ublox_msg ( ublox_msg , sm . logMonoTime [ ' ubloxGnss ' ] , block = replay )
if msg is not None :
pm . send ( ' gnssMeasurements ' , msg )
if not laikad . got_first_ublox_msg and sm . updated [ ' clocks ' ] :
clocks_msg = sm [ ' clocks ' ]
t = GPSTime . from_datetime ( datetime . utcfromtimestamp ( clocks_msg . wallTimeNanos * 1E-9 ) )
if laikad . auto_fetch_orbits :
laikad . fetch_orbits ( t , block = replay )
if __name__ == " __main__ " :