1
1
#!/usr/bin/env python3
2
+ import json
2
3
import time
3
4
from concurrent .futures import Future , ProcessPoolExecutor
4
5
from typing import List , Optional
9
10
from numpy .linalg import linalg
10
11
11
12
from cereal import log , messaging
13
+ from common .params import Params , put_nonblocking
12
14
from laika import AstroDog
13
15
from laika .constants import SECS_IN_HR , SECS_IN_MIN
14
- from laika .ephemeris import EphemerisType , convert_ublox_ephem
16
+ from laika .ephemeris import Ephemeris , EphemerisType , convert_ublox_ephem
15
17
from laika .gps_time import GPSTime
16
18
from laika .helpers import ConstellationId
17
19
from laika .raw_gnss import GNSSMeasurement , calc_pos_fix , correct_measurements , process_measurements , read_raw_ublox
22
24
from selfdrive .swaglog import cloudlog
23
25
24
26
MAX_TIME_GAP = 10
27
+ EPHEMERIS_CACHE = 'LaikadEphemeris'
28
+ CACHE_VERSION = 0.1
25
29
26
30
27
31
class Laikad :
28
-
29
- def __init__ ( self , valid_const = ( "GPS" , "GLONASS" ), auto_update = False , valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT , EphemerisType . NAV ) ):
30
- self .astro_dog = AstroDog (valid_const = valid_const , auto_update = auto_update , valid_ephem_types = valid_ephem_types )
32
+ def __init__ ( self , valid_const = ( "GPS" , "GLONASS" ), auto_update = False , valid_ephem_types = ( EphemerisType . ULTRA_RAPID_ORBIT , EphemerisType . NAV ),
33
+ save_ephemeris = False ):
34
+ self .astro_dog = AstroDog (valid_const = valid_const , auto_update = auto_update , valid_ephem_types = valid_ephem_types , clear_old_ephemeris = True )
31
35
self .gnss_kf = GNSSKalman (GENERATED_DIR )
32
36
self .orbit_fetch_executor = ProcessPoolExecutor ()
33
37
self .orbit_fetch_future : Optional [Future ] = None
34
38
self .last_fetch_orbits_t = None
39
+ self .last_cached_t = None
40
+ self .save_ephemeris = save_ephemeris
41
+ self .load_cache ()
42
+
43
+ def load_cache (self ):
44
+ cache = Params ().get (EPHEMERIS_CACHE )
45
+ if not cache :
46
+ return
47
+ try :
48
+ cache = json .loads (cache , object_hook = deserialize_hook )
49
+ self .astro_dog .add_orbits (cache ['orbits' ])
50
+ self .astro_dog .add_navs (cache ['nav' ])
51
+ self .last_fetch_orbits_t = cache ['last_fetch_orbits_t' ]
52
+ except json .decoder .JSONDecodeError :
53
+ cloudlog .exception ("Error parsing cache" )
54
+
55
+ def cache_ephemeris (self , t : GPSTime ):
56
+ if self .save_ephemeris and (self .last_cached_t is None or t - self .last_cached_t > SECS_IN_MIN ):
57
+ put_nonblocking (EPHEMERIS_CACHE , json .dumps (
58
+ {'version' : CACHE_VERSION , 'last_fetch_orbits_t' : self .last_fetch_orbits_t , 'orbits' : self .astro_dog .orbits , 'nav' : self .astro_dog .nav },
59
+ cls = CacheSerializer ))
60
+ self .last_cached_t = t
35
61
36
62
def process_ublox_msg (self , ublox_msg , ublox_mono_time : int , block = False ):
37
63
if ublox_msg .which == 'measurementReport' :
@@ -83,7 +109,8 @@ def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
83
109
return dat
84
110
elif ublox_msg .which == 'ephemeris' :
85
111
ephem = convert_ublox_ephem (ublox_msg .ephemeris )
86
- self .astro_dog .add_navs ([ephem ])
112
+ self .astro_dog .add_navs ({ephem .prn : [ephem ]})
113
+ self .cache_ephemeris (t = ephem .epoch )
87
114
# elif ublox_msg.which == 'ionoData':
88
115
# todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them.
89
116
@@ -101,7 +128,7 @@ def update_localizer(self, pos_fix, t: float, measurements: List[GNSSMeasurement
101
128
cloudlog .error ("Gnss kalman std too far" )
102
129
103
130
if len (pos_fix ) == 0 :
104
- cloudlog .warning ("Position fix not available when resetting kalman filter" )
131
+ cloudlog .info ("Position fix not available when resetting kalman filter" )
105
132
return
106
133
post_est = pos_fix [0 ][:3 ].tolist ()
107
134
self .init_gnss_localizer (post_est )
@@ -134,10 +161,11 @@ def fetch_orbits(self, t: GPSTime, block):
134
161
self .orbit_fetch_future .result ()
135
162
if self .orbit_fetch_future .done ():
136
163
ret = self .orbit_fetch_future .result ()
164
+ self .last_fetch_orbits_t = t
137
165
if ret :
138
166
self .astro_dog .orbits , self .astro_dog .orbit_fetched_times = ret
167
+ self .cache_ephemeris (t = t )
139
168
self .orbit_fetch_future = None
140
- self .last_fetch_orbits_t = t
141
169
142
170
143
171
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):
193
221
return float (np .rad2deg (bearing )), float (bearing_std )
194
222
195
223
224
+ class CacheSerializer (json .JSONEncoder ):
225
+
226
+ def default (self , o ):
227
+ if isinstance (o , Ephemeris ):
228
+ return o .to_json ()
229
+ if isinstance (o , GPSTime ):
230
+ return o .__dict__
231
+ if isinstance (o , np .ndarray ):
232
+ return o .tolist ()
233
+ return json .JSONEncoder .default (self , o )
234
+
235
+
236
+ def deserialize_hook (dct ):
237
+ if 'ephemeris' in dct :
238
+ return Ephemeris .from_json (dct )
239
+ if 'week' in dct :
240
+ return GPSTime (dct ['week' ], dct ['tow' ])
241
+ return dct
242
+
243
+
196
244
def main ():
197
245
sm = messaging .SubMaster (['ubloxGnss' ])
198
246
pm = messaging .PubMaster (['gnssMeasurements' ])
199
247
200
- laikad = Laikad ()
248
+ laikad = Laikad (save_ephemeris = True )
201
249
while True :
202
250
sm .update ()
203
251
0 commit comments