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