7
7
import numpy as np
8
8
from collections import defaultdict
9
9
10
+ import sympy
10
11
from numpy .linalg import linalg
11
12
12
13
from cereal import log , messaging
13
14
from common .params import Params , put_nonblocking
14
15
from laika import AstroDog
15
- from laika .constants import SECS_IN_HR , SECS_IN_MIN
16
+ from laika .constants import EARTH_ROTATION_RATE , SECS_IN_HR , SECS_IN_MIN , SPEED_OF_LIGHT
16
17
from laika .ephemeris import Ephemeris , EphemerisType , convert_ublox_ephem
17
18
from laika .gps_time import GPSTime
18
19
from laika .helpers import ConstellationId
19
- from laika .raw_gnss import GNSSMeasurement , calc_pos_fix , correct_measurements , process_measurements , read_raw_ublox
20
+ from laika .raw_gnss import GNSSMeasurement , correct_measurements , process_measurements , read_raw_ublox
20
21
from selfdrive .locationd .models .constants import GENERATED_DIR , ObservationKind
21
22
from selfdrive .locationd .models .gnss_kf import GNSSKalman
22
23
from selfdrive .locationd .models .gnss_kf import States as GStates
@@ -38,6 +39,7 @@ def __init__(self, valid_const=("GPS", "GLONASS"), auto_update=False, valid_ephe
38
39
self .last_cached_t = None
39
40
self .save_ephemeris = save_ephemeris
40
41
self .load_cache ()
42
+ self .posfix_functions = {constellation : get_posfix_sympy_fun (constellation ) for constellation in (ConstellationId .GPS , ConstellationId .GLONASS )}
41
43
42
44
def load_cache (self ):
43
45
cache = Params ().get (EPHEMERIS_CACHE )
@@ -66,7 +68,9 @@ def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
66
68
self .fetch_orbits (latest_msg_t + SECS_IN_MIN , block )
67
69
new_meas = read_raw_ublox (report )
68
70
processed_measurements = process_measurements (new_meas , self .astro_dog )
69
- pos_fix = calc_pos_fix (processed_measurements , min_measurements = 4 )
71
+
72
+ min_measurements = 5 if any (p .constellation_id == ConstellationId .GLONASS for p in processed_measurements ) else 4
73
+ pos_fix = calc_pos_fix_gauss_newton (processed_measurements , self .posfix_functions , min_measurements = min_measurements )
70
74
71
75
t = ublox_mono_time * 1e-9
72
76
kf_pos_std = None
@@ -84,7 +88,7 @@ def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
84
88
if est_pos is not None :
85
89
corrected_measurements = correct_measurements (processed_measurements , est_pos , self .astro_dog )
86
90
87
- self .update_localizer (pos_fix , t , corrected_measurements )
91
+ self .update_localizer (est_pos , t , corrected_measurements )
88
92
kf_valid = all (self .kf_valid (t ))
89
93
90
94
ecef_pos = self .gnss_kf .x [GStates .ECEF_POS ].tolist ()
@@ -110,7 +114,7 @@ def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
110
114
# elif ublox_msg.which == 'ionoData':
111
115
# todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them.
112
116
113
- def update_localizer (self , pos_fix , t : float , measurements : List [GNSSMeasurement ]):
117
+ def update_localizer (self , est_pos , t : float , measurements : List [GNSSMeasurement ]):
114
118
# Check time and outputs are valid
115
119
valid = self .kf_valid (t )
116
120
if not all (valid ):
@@ -123,11 +127,10 @@ def update_localizer(self, pos_fix, t: float, measurements: List[GNSSMeasurement
123
127
else :
124
128
cloudlog .error ("Gnss kalman std too far" )
125
129
126
- if len ( pos_fix ) == 0 :
130
+ if est_pos is None :
127
131
cloudlog .info ("Position fix not available when resetting kalman filter" )
128
132
return
129
- post_est = pos_fix [0 ][:3 ].tolist ()
130
- self .init_gnss_localizer (post_est )
133
+ self .init_gnss_localizer (est_pos .tolist ())
131
134
if len (measurements ) > 0 :
132
135
kf_add_observations (self .gnss_kf , t , measurements )
133
136
else :
@@ -227,6 +230,90 @@ def deserialize_hook(dct):
227
230
return dct
228
231
229
232
233
+ def calc_pos_fix_gauss_newton (measurements , posfix_functions , x0 = None , signal = 'C1C' , min_measurements = 6 ):
234
+ '''
235
+ Calculates gps fix using gauss newton method
236
+ To solve the problem a minimal of 4 measurements are required.
237
+ If Glonass is included 5 are required to solve for the additional free variable.
238
+ returns:
239
+ 0 -> list with positions
240
+ '''
241
+ if x0 is None :
242
+ x0 = [0 , 0 , 0 , 0 , 0 ]
243
+ n = len (measurements )
244
+ if n < min_measurements :
245
+ return []
246
+
247
+ Fx_pos = pr_residual (measurements , posfix_functions , signal = signal )
248
+ x = gauss_newton (Fx_pos , x0 )
249
+ residual , _ = Fx_pos (x , weight = 1.0 )
250
+ return x , residual
251
+
252
+
253
+ def pr_residual (measurements , posfix_functions , signal = 'C1C' ):
254
+ def Fx_pos (inp , weight = None ):
255
+ vals , gradients = [], []
256
+
257
+ for meas in measurements :
258
+ pr = meas .observables [signal ]
259
+ pr += meas .sat_clock_err * SPEED_OF_LIGHT
260
+
261
+ w = (1 / meas .observables_std [signal ]) if weight is None else weight
262
+
263
+ val , * gradient = posfix_functions [meas .constellation_id ](* inp , pr , * meas .sat_pos , w )
264
+ vals .append (val )
265
+ gradients .append (gradient )
266
+ return np .asarray (vals ), np .asarray (gradients )
267
+
268
+ return Fx_pos
269
+
270
+
271
+ def gauss_newton (fun , b , xtol = 1e-8 , max_n = 25 ):
272
+ for _ in range (max_n ):
273
+ # Compute function and jacobian on current estimate
274
+ r , J = fun (b )
275
+
276
+ # Update estimate
277
+ delta = np .linalg .pinv (J ) @ r
278
+ b -= delta
279
+
280
+ # Check step size for stopping condition
281
+ if np .linalg .norm (delta ) < xtol :
282
+ break
283
+ return b
284
+
285
+
286
+ def get_posfix_sympy_fun (constellation ):
287
+ # Unknowns
288
+ x , y , z = sympy .Symbol ('x' ), sympy .Symbol ('y' ), sympy .Symbol ('z' )
289
+ bc = sympy .Symbol ('bc' )
290
+ bg = sympy .Symbol ('bg' )
291
+ var = [x , y , z , bc , bg ]
292
+
293
+ # Knowns
294
+ pr = sympy .Symbol ('pr' )
295
+ sat_x , sat_y , sat_z = sympy .Symbol ('sat_x' ), sympy .Symbol ('sat_y' ), sympy .Symbol ('sat_z' )
296
+ weight = sympy .Symbol ('weight' )
297
+
298
+ theta = EARTH_ROTATION_RATE * (pr - bc ) / SPEED_OF_LIGHT
299
+ val = sympy .sqrt (
300
+ (sat_x * sympy .cos (theta ) + sat_y * sympy .sin (theta ) - x ) ** 2 +
301
+ (sat_y * sympy .cos (theta ) - sat_x * sympy .sin (theta ) - y ) ** 2 +
302
+ (sat_z - z ) ** 2
303
+ )
304
+
305
+ if constellation == ConstellationId .GLONASS :
306
+ res = weight * (val - (pr - bc - bg ))
307
+ elif constellation == ConstellationId .GPS :
308
+ res = weight * (val - (pr - bc ))
309
+ else :
310
+ raise NotImplementedError (f"Constellation { constellation } not supported" )
311
+
312
+ res = [res ] + [sympy .diff (res , v ) for v in var ]
313
+
314
+ return sympy .lambdify ([x , y , z , bc , bg , pr , sat_x , sat_y , sat_z , weight ], res )
315
+
316
+
230
317
def main ():
231
318
sm = messaging .SubMaster (['ubloxGnss' ])
232
319
pm = messaging .PubMaster (['gnssMeasurements' ])
0 commit comments