parent
							
								
									ea02548652
								
							
						
					
					
						commit
						dda315bcc8
					
				
				 46 changed files with 6157 additions and 0 deletions
			
			
		@ -0,0 +1,4 @@ | 
				
			|||||||
 | 
					ubloxd | 
				
			||||||
 | 
					ubloxd_test | 
				
			||||||
 | 
					params_learner | 
				
			||||||
 | 
					paramsd | 
				
			||||||
@ -0,0 +1,25 @@ | 
				
			|||||||
 | 
					Import('env', 'common', 'messaging') | 
				
			||||||
 | 
					loc_objs = [ | 
				
			||||||
 | 
					  "locationd_yawrate.cc", | 
				
			||||||
 | 
					  "params_learner.cc", | 
				
			||||||
 | 
					  "paramsd.cc"] | 
				
			||||||
 | 
					loc_libs = [messaging, 'zmq', common, 'capnp', 'kj', 'json', 'json11', 'pthread'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env.Program("paramsd", loc_objs, LIBS=loc_libs) | 
				
			||||||
 | 
					env.SharedLibrary("locationd", loc_objs, LIBS=loc_libs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env.Program("ubloxd", [ | 
				
			||||||
 | 
					  "ubloxd.cc", | 
				
			||||||
 | 
					  "ublox_msg.cc", | 
				
			||||||
 | 
					  "ubloxd_main.cc"], | 
				
			||||||
 | 
					  LIBS=loc_libs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env.Program("ubloxd_test", [ | 
				
			||||||
 | 
					  "ubloxd_test.cc", | 
				
			||||||
 | 
					  "ublox_msg.cc", | 
				
			||||||
 | 
					  "ubloxd_main.cc"], | 
				
			||||||
 | 
					  LIBS=loc_libs) | 
				
			||||||
 | 
					   | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,11 @@ | 
				
			|||||||
 | 
					import math | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Filter: | 
				
			||||||
 | 
					  MIN_SPEED = 7 # m/s  (~15.5mph) | 
				
			||||||
 | 
					  MAX_YAW_RATE = math.radians(3) # per second | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Calibration: | 
				
			||||||
 | 
					  UNCALIBRATED = 0 | 
				
			||||||
 | 
					  CALIBRATED = 1 | 
				
			||||||
 | 
					  INVALID = 2 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,164 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import copy | 
				
			||||||
 | 
					import json | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					from selfdrive.locationd.calibration_helpers import Calibration | 
				
			||||||
 | 
					from selfdrive.swaglog import cloudlog | 
				
			||||||
 | 
					from common.params import Params, put_nonblocking | 
				
			||||||
 | 
					from common.transformations.model import model_height | 
				
			||||||
 | 
					from common.transformations.camera import view_frame_from_device_frame, get_view_frame_from_road_frame, \ | 
				
			||||||
 | 
					                                          get_calib_from_vp, H, W, FOCAL | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MPH_TO_MS = 0.44704 | 
				
			||||||
 | 
					MIN_SPEED_FILTER = 15 * MPH_TO_MS | 
				
			||||||
 | 
					MAX_SPEED_STD = 1.5 | 
				
			||||||
 | 
					MAX_YAW_RATE_FILTER = np.radians(2)  # per second | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This is all 20Hz, blocks needed for efficiency | 
				
			||||||
 | 
					BLOCK_SIZE = 100 | 
				
			||||||
 | 
					INPUTS_NEEDED = 5   # allow to update VP every so many frames | 
				
			||||||
 | 
					INPUTS_WANTED = 20   # We want a little bit more than we need for stability | 
				
			||||||
 | 
					WRITE_CYCLES = 10  # write every 1000 cycles | 
				
			||||||
 | 
					VP_INIT = np.array([W/2., H/2.]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# These validity corners were chosen by looking at 1000 | 
				
			||||||
 | 
					# and taking most extreme cases with some margin. | 
				
			||||||
 | 
					VP_VALIDITY_CORNERS = np.array([[W//2 - 120, 300], [W//2 + 120, 520]]) | 
				
			||||||
 | 
					DEBUG = os.getenv("DEBUG") is not None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_calibration_valid(vp): | 
				
			||||||
 | 
					  return vp[0] > VP_VALIDITY_CORNERS[0,0] and vp[0] < VP_VALIDITY_CORNERS[1,0] and \ | 
				
			||||||
 | 
					         vp[1] > VP_VALIDITY_CORNERS[0,1] and vp[1] < VP_VALIDITY_CORNERS[1,1] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sanity_clip(vp): | 
				
			||||||
 | 
					  if np.isnan(vp).any(): | 
				
			||||||
 | 
					    vp = VP_INIT | 
				
			||||||
 | 
					  return [np.clip(vp[0], VP_VALIDITY_CORNERS[0,0] - 20, VP_VALIDITY_CORNERS[1,0] + 20), | 
				
			||||||
 | 
					          np.clip(vp[1], VP_VALIDITY_CORNERS[0,1] - 20, VP_VALIDITY_CORNERS[1,1] + 20)] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def intrinsics_from_vp(vp): | 
				
			||||||
 | 
					  return np.array([ | 
				
			||||||
 | 
					    [FOCAL,   0.,   vp[0]], | 
				
			||||||
 | 
					    [  0.,  FOCAL,  vp[1]], | 
				
			||||||
 | 
					    [  0.,    0.,     1.]]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Calibrator(): | 
				
			||||||
 | 
					  def __init__(self, param_put=False): | 
				
			||||||
 | 
					    self.param_put = param_put | 
				
			||||||
 | 
					    self.vp = copy.copy(VP_INIT) | 
				
			||||||
 | 
					    self.vps = np.zeros((INPUTS_WANTED, 2)) | 
				
			||||||
 | 
					    self.idx = 0 | 
				
			||||||
 | 
					    self.block_idx = 0 | 
				
			||||||
 | 
					    self.valid_blocks = 0 | 
				
			||||||
 | 
					    self.cal_status = Calibration.UNCALIBRATED | 
				
			||||||
 | 
					    self.just_calibrated = False | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Read calibration | 
				
			||||||
 | 
					    calibration_params = Params().get("CalibrationParams") | 
				
			||||||
 | 
					    if calibration_params: | 
				
			||||||
 | 
					      try: | 
				
			||||||
 | 
					        calibration_params = json.loads(calibration_params) | 
				
			||||||
 | 
					        self.vp = np.array(calibration_params["vanishing_point"]) | 
				
			||||||
 | 
					        if not np.isfinite(self.vp).all(): | 
				
			||||||
 | 
					          self.vp = copy.copy(VP_INIT) | 
				
			||||||
 | 
					        self.vps = np.tile(self.vp, (INPUTS_WANTED, 1)) | 
				
			||||||
 | 
					        self.valid_blocks = calibration_params['valid_blocks'] | 
				
			||||||
 | 
					        if not np.isfinite(self.valid_blocks) or self.valid_blocks < 0: | 
				
			||||||
 | 
					          self.valid_blocks = 0 | 
				
			||||||
 | 
					        self.update_status() | 
				
			||||||
 | 
					      except Exception: | 
				
			||||||
 | 
					        cloudlog.exception("CalibrationParams file found but error encountered") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update_status(self): | 
				
			||||||
 | 
					    start_status = self.cal_status | 
				
			||||||
 | 
					    if self.valid_blocks < INPUTS_NEEDED: | 
				
			||||||
 | 
					      self.cal_status = Calibration.UNCALIBRATED | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.cal_status = Calibration.CALIBRATED if is_calibration_valid(self.vp) else Calibration.INVALID | 
				
			||||||
 | 
					    end_status = self.cal_status | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.just_calibrated = False | 
				
			||||||
 | 
					    if start_status == Calibration.UNCALIBRATED and end_status == Calibration.CALIBRATED: | 
				
			||||||
 | 
					      self.just_calibrated = True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_cam_odom(self, trans, rot, trans_std, rot_std): | 
				
			||||||
 | 
					    if ((trans[0] > MIN_SPEED_FILTER) and | 
				
			||||||
 | 
					        (trans_std[0] < MAX_SPEED_STD) and | 
				
			||||||
 | 
					         (abs(rot[2]) < MAX_YAW_RATE_FILTER)): | 
				
			||||||
 | 
					      # intrinsics are not eon intrinsics, since this is calibrated frame | 
				
			||||||
 | 
					      intrinsics = intrinsics_from_vp(self.vp) | 
				
			||||||
 | 
					      new_vp = intrinsics.dot(view_frame_from_device_frame.dot(trans)) | 
				
			||||||
 | 
					      new_vp = new_vp[:2]/new_vp[2] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.vps[self.block_idx] = (self.idx*self.vps[self.block_idx] + (BLOCK_SIZE - self.idx) * new_vp) / float(BLOCK_SIZE) | 
				
			||||||
 | 
					      self.idx = (self.idx + 1) % BLOCK_SIZE | 
				
			||||||
 | 
					      if self.idx == 0: | 
				
			||||||
 | 
					        self.block_idx += 1 | 
				
			||||||
 | 
					        self.valid_blocks = max(self.block_idx, self.valid_blocks) | 
				
			||||||
 | 
					        self.block_idx = self.block_idx % INPUTS_WANTED | 
				
			||||||
 | 
					      raw_vp = np.mean(self.vps[:max(1, self.valid_blocks)], axis=0) | 
				
			||||||
 | 
					      self.vp = sanity_clip(raw_vp) | 
				
			||||||
 | 
					      self.update_status() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if self.param_put and ((self.idx == 0 and self.block_idx == 0) or self.just_calibrated): | 
				
			||||||
 | 
					        cal_params = {"vanishing_point": list(self.vp), | 
				
			||||||
 | 
					                      "valid_blocks": self.valid_blocks} | 
				
			||||||
 | 
					        put_nonblocking("CalibrationParams", json.dumps(cal_params).encode('utf8')) | 
				
			||||||
 | 
					      return new_vp | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      return None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send_data(self, pm): | 
				
			||||||
 | 
					    calib = get_calib_from_vp(self.vp) | 
				
			||||||
 | 
					    extrinsic_matrix = get_view_frame_from_road_frame(0, calib[1], calib[2], model_height) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cal_send = messaging.new_message() | 
				
			||||||
 | 
					    cal_send.init('liveCalibration') | 
				
			||||||
 | 
					    cal_send.liveCalibration.calStatus = self.cal_status | 
				
			||||||
 | 
					    cal_send.liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) | 
				
			||||||
 | 
					    cal_send.liveCalibration.extrinsicMatrix = [float(x) for x in extrinsic_matrix.flatten()] | 
				
			||||||
 | 
					    cal_send.liveCalibration.rpyCalib = [float(x) for x in calib] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pm.send('liveCalibration', cal_send) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def calibrationd_thread(sm=None, pm=None): | 
				
			||||||
 | 
					  if sm is None: | 
				
			||||||
 | 
					    sm = messaging.SubMaster(['cameraOdometry']) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if pm is None: | 
				
			||||||
 | 
					    pm = messaging.PubMaster(['liveCalibration']) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  calibrator = Calibrator(param_put=True) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  send_counter = 0 | 
				
			||||||
 | 
					  while 1: | 
				
			||||||
 | 
					    sm.update() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if sm.updated['cameraOdometry']: | 
				
			||||||
 | 
					      new_vp = calibrator.handle_cam_odom(sm['cameraOdometry'].trans, | 
				
			||||||
 | 
					                                          sm['cameraOdometry'].rot, | 
				
			||||||
 | 
					                                          sm['cameraOdometry'].transStd, | 
				
			||||||
 | 
					                                          sm['cameraOdometry'].rotStd) | 
				
			||||||
 | 
					    if DEBUG and new_vp is not None: | 
				
			||||||
 | 
					      print('got new vp', new_vp) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # decimate outputs for efficiency | 
				
			||||||
 | 
					    if (send_counter % 5) == 0: | 
				
			||||||
 | 
					      calibrator.send_data(pm) | 
				
			||||||
 | 
					    send_counter += 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(sm=None, pm=None): | 
				
			||||||
 | 
					  calibrationd_thread(sm, pm) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
@ -0,0 +1,4 @@ | 
				
			|||||||
 | 
					lane.cpp | 
				
			||||||
 | 
					gnss.cpp | 
				
			||||||
 | 
					loc*.cpp | 
				
			||||||
 | 
					pos_computer*.cpp | 
				
			||||||
@ -0,0 +1,21 @@ | 
				
			|||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_chi2_ppf_lookup(max_dim=200): | 
				
			||||||
 | 
					  from scipy.stats import chi2 | 
				
			||||||
 | 
					  table = np.zeros((max_dim, 98)) | 
				
			||||||
 | 
					  for dim in range(1,max_dim): | 
				
			||||||
 | 
					    table[dim] = chi2.ppf(np.arange(.01, .99, .01), dim) | 
				
			||||||
 | 
					  #outfile = open('chi2_lookup_table', 'w') | 
				
			||||||
 | 
					  np.save('chi2_lookup_table', table) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def chi2_ppf(p, dim): | 
				
			||||||
 | 
					  table = np.load(os.path.dirname(os.path.realpath(__file__)) + '/chi2_lookup_table.npy') | 
				
			||||||
 | 
					  result = np.interp(p, np.arange(.01, .99, .01), table[dim]) | 
				
			||||||
 | 
					  return result | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__== "__main__": | 
				
			||||||
 | 
					  gen_chi2_ppf_lookup() | 
				
			||||||
@ -0,0 +1,3 @@ | 
				
			|||||||
 | 
					version https://git-lfs.github.com/spec/v1 | 
				
			||||||
 | 
					oid sha256:0cc301b3a918cce76bd119a219e31722c6a03fa837927eb10fd84e877966cc3e | 
				
			||||||
 | 
					size 156928 | 
				
			||||||
@ -0,0 +1,51 @@ | 
				
			|||||||
 | 
					#include <eigen3/Eigen/QR>     | 
				
			||||||
 | 
					#include <eigen3/Eigen/Dense>     | 
				
			||||||
 | 
					#include <iostream>     | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, KDIM*2, 3, Eigen::RowMajor> R3M; | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, KDIM*2, 1> R1M; | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, 3, 1> O1M; | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, 3, 3, Eigen::RowMajor> M3D; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void gauss_newton(double *in_x, double *in_poses, double *in_img_positions) { | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  double res[KDIM*2] = {0}; | 
				
			||||||
 | 
					  double jac[KDIM*6] = {0}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  O1M x(in_x); | 
				
			||||||
 | 
					  O1M delta; | 
				
			||||||
 | 
					  int counter = 0; | 
				
			||||||
 | 
					  while ((delta.squaredNorm() > 0.0001 and counter < 30) or counter == 0){ | 
				
			||||||
 | 
					    res_fun(in_x, in_poses, in_img_positions, res); | 
				
			||||||
 | 
					    jac_fun(in_x, in_poses, in_img_positions, jac); | 
				
			||||||
 | 
					    R1M E(res); R3M J(jac); | 
				
			||||||
 | 
					    delta = (J.transpose()*J).inverse() * J.transpose() * E; | 
				
			||||||
 | 
					    x = x - delta; | 
				
			||||||
 | 
					    memcpy(in_x, x.data(), 3 * sizeof(double)); | 
				
			||||||
 | 
					    counter = counter + 1; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void compute_pos(double *to_c, double *poses, double *img_positions, double *param, double *pos) { | 
				
			||||||
 | 
					    param[0] = img_positions[KDIM*2-2]; | 
				
			||||||
 | 
					    param[1] = img_positions[KDIM*2-1]; 
 | 
				
			||||||
 | 
					    param[2] = 0.1; | 
				
			||||||
 | 
					    gauss_newton(param, poses, img_positions); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Eigen::Quaterniond q; | 
				
			||||||
 | 
					    q.w() = poses[KDIM*7-4]; | 
				
			||||||
 | 
					    q.x() = poses[KDIM*7-3]; | 
				
			||||||
 | 
					    q.y() = poses[KDIM*7-2]; | 
				
			||||||
 | 
					    q.z() = poses[KDIM*7-1]; | 
				
			||||||
 | 
					    M3D RC(to_c); | 
				
			||||||
 | 
					    Eigen::Matrix3d R = q.normalized().toRotationMatrix(); | 
				
			||||||
 | 
					    Eigen::Matrix3d rot = R * RC.transpose(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pos[0] = param[0]/param[2]; | 
				
			||||||
 | 
					    pos[1] = param[1]/param[2]; | 
				
			||||||
 | 
					    pos[2] = 1.0/param[2]; | 
				
			||||||
 | 
					    O1M ecef_offset(poses + KDIM*7-7); | 
				
			||||||
 | 
					    O1M ecef_output(pos); | 
				
			||||||
 | 
					    ecef_output = rot*ecef_output + ecef_offset; | 
				
			||||||
 | 
					    memcpy(pos, ecef_output.data(), 3 * sizeof(double)); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,123 @@ | 
				
			|||||||
 | 
					#include <eigen3/Eigen/Dense> | 
				
			||||||
 | 
					#include <iostream> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, DIM, DIM, Eigen::RowMajor> DDM; | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, EDIM, EDIM, Eigen::RowMajor> EEM; | 
				
			||||||
 | 
					typedef Eigen::Matrix<double, DIM, EDIM, Eigen::RowMajor> DEM; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void predict(double *in_x, double *in_P, double *in_Q, double dt) { | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, MEDIM, MEDIM, Eigen::RowMajor> RRM; | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  double nx[DIM] = {0}; | 
				
			||||||
 | 
					  double in_F[EDIM*EDIM] = {0}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // functions from sympy
 | 
				
			||||||
 | 
					  f_fun(in_x, dt, nx); | 
				
			||||||
 | 
					  F_fun(in_x, dt, in_F); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EEM F(in_F); | 
				
			||||||
 | 
					  EEM P(in_P); | 
				
			||||||
 | 
					  EEM Q(in_Q); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RRM F_main = F.topLeftCorner(MEDIM, MEDIM); | 
				
			||||||
 | 
					  P.topLeftCorner(MEDIM, MEDIM) = (F_main * P.topLeftCorner(MEDIM, MEDIM)) * F_main.transpose(); | 
				
			||||||
 | 
					  P.topRightCorner(MEDIM, EDIM - MEDIM) = F_main * P.topRightCorner(MEDIM, EDIM - MEDIM); | 
				
			||||||
 | 
					  P.bottomLeftCorner(EDIM - MEDIM, MEDIM) = P.bottomLeftCorner(EDIM - MEDIM, MEDIM) * F_main.transpose(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  P = P + dt*Q; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // copy out state
 | 
				
			||||||
 | 
					  memcpy(in_x, nx, DIM * sizeof(double)); | 
				
			||||||
 | 
					  memcpy(in_P, P.data(), EDIM * EDIM * sizeof(double)); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// note: extra_args dim only correct when null space projecting
 | 
				
			||||||
 | 
					// otherwise 1
 | 
				
			||||||
 | 
					template <int ZDIM, int EADIM, bool MAHA_TEST> | 
				
			||||||
 | 
					void update(double *in_x, double *in_P, Hfun h_fun, Hfun H_fun, Hfun Hea_fun, double *in_z, double *in_R, double *in_ea, double MAHA_THRESHOLD) { | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, ZDIM, ZDIM, Eigen::RowMajor> ZZM; | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, ZDIM, DIM, Eigen::RowMajor> ZDM; | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, Eigen::Dynamic, EDIM, Eigen::RowMajor> XEM; | 
				
			||||||
 | 
					  //typedef Eigen::Matrix<double, EDIM, ZDIM, Eigen::RowMajor> EZM;
 | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, Eigen::Dynamic, 1> X1M; | 
				
			||||||
 | 
					  typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> XXM; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double in_hx[ZDIM] = {0}; | 
				
			||||||
 | 
					  double in_H[ZDIM * DIM] = {0}; | 
				
			||||||
 | 
					  double in_H_mod[EDIM * DIM] = {0}; | 
				
			||||||
 | 
					  double delta_x[EDIM] = {0}; | 
				
			||||||
 | 
					  double x_new[DIM] = {0}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // state x, P
 | 
				
			||||||
 | 
					  Eigen::Matrix<double, ZDIM, 1> z(in_z); | 
				
			||||||
 | 
					  EEM P(in_P); | 
				
			||||||
 | 
					  ZZM pre_R(in_R); | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // functions from sympy
 | 
				
			||||||
 | 
					  h_fun(in_x, in_ea, in_hx); | 
				
			||||||
 | 
					  H_fun(in_x, in_ea, in_H); | 
				
			||||||
 | 
					  ZDM pre_H(in_H); 
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // get y (y = z - hx)
 | 
				
			||||||
 | 
					  Eigen::Matrix<double, ZDIM, 1> pre_y(in_hx); pre_y = z - pre_y; | 
				
			||||||
 | 
					  X1M y; XXM H; XXM R; | 
				
			||||||
 | 
					  if (Hea_fun){ | 
				
			||||||
 | 
					    typedef Eigen::Matrix<double, ZDIM, EADIM, Eigen::RowMajor> ZAM; | 
				
			||||||
 | 
					    double in_Hea[ZDIM * EADIM] = {0}; | 
				
			||||||
 | 
					    Hea_fun(in_x, in_ea, in_Hea); | 
				
			||||||
 | 
					    ZAM Hea(in_Hea); | 
				
			||||||
 | 
					    XXM A = Hea.transpose().fullPivLu().kernel(); | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    y = A.transpose() * pre_y; | 
				
			||||||
 | 
					    H = A.transpose() * pre_H; | 
				
			||||||
 | 
					    R = A.transpose() * pre_R * A; | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    y = pre_y; | 
				
			||||||
 | 
					    H = pre_H; | 
				
			||||||
 | 
					    R = pre_R; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  // get modified H
 | 
				
			||||||
 | 
					  H_mod_fun(in_x, in_H_mod); | 
				
			||||||
 | 
					  DEM H_mod(in_H_mod); | 
				
			||||||
 | 
					  XEM H_err = H * H_mod; | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Do mahalobis distance test
 | 
				
			||||||
 | 
					  if (MAHA_TEST){ | 
				
			||||||
 | 
					    XXM a = (H_err * P * H_err.transpose() + R).inverse(); | 
				
			||||||
 | 
					    double maha_dist = y.transpose() * a * y; | 
				
			||||||
 | 
					    if (maha_dist > MAHA_THRESHOLD){ | 
				
			||||||
 | 
					      R = 1.0e16 * R; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Outlier resilient weighting
 | 
				
			||||||
 | 
					  double weight = 1;//(1.5)/(1 + y.squaredNorm()/R.sum());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // kalman gains and I_KH
 | 
				
			||||||
 | 
					  XXM S = ((H_err * P) * H_err.transpose()) + R/weight; | 
				
			||||||
 | 
					  XEM KT = S.fullPivLu().solve(H_err * P.transpose()); | 
				
			||||||
 | 
					  //EZM K = KT.transpose(); TODO: WHY DOES THIS NOT COMPILE?
 | 
				
			||||||
 | 
					  //EZM K = S.fullPivLu().solve(H_err * P.transpose()).transpose();
 | 
				
			||||||
 | 
					  //std::cout << "Here is the matrix rot:\n" << K << std::endl;
 | 
				
			||||||
 | 
					  EEM I_KH = Eigen::Matrix<double, EDIM, EDIM>::Identity() - (KT.transpose() * H_err); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // update state by injecting dx
 | 
				
			||||||
 | 
					  Eigen::Matrix<double, EDIM, 1> dx(delta_x); | 
				
			||||||
 | 
					  dx  = (KT.transpose() * y); | 
				
			||||||
 | 
					  memcpy(delta_x, dx.data(), EDIM * sizeof(double)); | 
				
			||||||
 | 
					  err_fun(in_x, delta_x, x_new); | 
				
			||||||
 | 
					  Eigen::Matrix<double, DIM, 1> x(x_new); | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					  // update cov 
 | 
				
			||||||
 | 
					  P = ((I_KH * P) * I_KH.transpose()) + ((KT.transpose() * R) * KT); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // copy out state
 | 
				
			||||||
 | 
					  memcpy(in_x, x.data(), DIM * sizeof(double)); | 
				
			||||||
 | 
					  memcpy(in_P, P.data(), EDIM * EDIM * sizeof(double)); | 
				
			||||||
 | 
					  memcpy(in_z, y.data(), y.rows() * sizeof(double)); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,562 @@ | 
				
			|||||||
 | 
					import os | 
				
			||||||
 | 
					from bisect import bisect_right | 
				
			||||||
 | 
					import sympy as sp | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from numpy import dot | 
				
			||||||
 | 
					from common.ffi_wrapper import compile_code, wrap_compiled | 
				
			||||||
 | 
					from common.sympy_helpers import sympy_into_c | 
				
			||||||
 | 
					from .chi2_lookup import chi2_ppf | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXTERNAL_PATH = os.path.dirname(os.path.abspath(__file__)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def solve(a, b): | 
				
			||||||
 | 
					  if a.shape[0] == 1 and a.shape[1] == 1: | 
				
			||||||
 | 
					    #assert np.allclose(b/a[0][0], np.linalg.solve(a, b)) | 
				
			||||||
 | 
					    return b/a[0][0] | 
				
			||||||
 | 
					  else: | 
				
			||||||
 | 
					    return np.linalg.solve(a, b) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def null(H, eps=1e-12): | 
				
			||||||
 | 
					  u, s, vh = np.linalg.svd(H) | 
				
			||||||
 | 
					  padding = max(0,np.shape(H)[1]-np.shape(s)[0]) | 
				
			||||||
 | 
					  null_mask = np.concatenate(((s <= eps), np.ones((padding,),dtype=bool)),axis=0) | 
				
			||||||
 | 
					  null_space = np.compress(null_mask, vh, axis=0) | 
				
			||||||
 | 
					  return np.transpose(null_space) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_code(name, f_sym, dt_sym, x_sym, obs_eqs, dim_x, dim_err, eskf_params=None, msckf_params=None, maha_test_kinds=[]): | 
				
			||||||
 | 
					  # optional state transition matrix, H modifier | 
				
			||||||
 | 
					  # and err_function if an error-state kalman filter (ESKF) | 
				
			||||||
 | 
					  # is desired. Best described in "Quaternion kinematics | 
				
			||||||
 | 
					  # for the error-state Kalman filter" by Joan Sola | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if eskf_params: | 
				
			||||||
 | 
					    err_eqs = eskf_params[0] | 
				
			||||||
 | 
					    inv_err_eqs = eskf_params[1] | 
				
			||||||
 | 
					    H_mod_sym = eskf_params[2] | 
				
			||||||
 | 
					    f_err_sym = eskf_params[3] | 
				
			||||||
 | 
					    x_err_sym = eskf_params[4] | 
				
			||||||
 | 
					  else: | 
				
			||||||
 | 
					    nom_x = sp.MatrixSymbol('nom_x',dim_x,1) | 
				
			||||||
 | 
					    true_x = sp.MatrixSymbol('true_x',dim_x,1) | 
				
			||||||
 | 
					    delta_x = sp.MatrixSymbol('delta_x',dim_x,1) | 
				
			||||||
 | 
					    err_function_sym = sp.Matrix(nom_x + delta_x) | 
				
			||||||
 | 
					    inv_err_function_sym = sp.Matrix(true_x - nom_x) | 
				
			||||||
 | 
					    err_eqs = [err_function_sym, nom_x, delta_x] | 
				
			||||||
 | 
					    inv_err_eqs = [inv_err_function_sym, nom_x, true_x] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    H_mod_sym = sp.Matrix(np.eye(dim_x)) | 
				
			||||||
 | 
					    f_err_sym = f_sym | 
				
			||||||
 | 
					    x_err_sym = x_sym | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # This configures the multi-state augmentation | 
				
			||||||
 | 
					  # needed for EKF-SLAM with MSCKF (Mourikis et al 2007) | 
				
			||||||
 | 
					  if msckf_params: | 
				
			||||||
 | 
					    msckf = True | 
				
			||||||
 | 
					    dim_main = msckf_params[0]      # size of the main state | 
				
			||||||
 | 
					    dim_augment = msckf_params[1]   # size of one augment state chunk | 
				
			||||||
 | 
					    dim_main_err = msckf_params[2] | 
				
			||||||
 | 
					    dim_augment_err = msckf_params[3] | 
				
			||||||
 | 
					    N = msckf_params[4] | 
				
			||||||
 | 
					    feature_track_kinds = msckf_params[5] | 
				
			||||||
 | 
					    assert dim_main + dim_augment*N == dim_x | 
				
			||||||
 | 
					    assert dim_main_err + dim_augment_err*N == dim_err | 
				
			||||||
 | 
					  else: | 
				
			||||||
 | 
					    msckf = False | 
				
			||||||
 | 
					    dim_main = dim_x | 
				
			||||||
 | 
					    dim_augment = 0 | 
				
			||||||
 | 
					    dim_main_err = dim_err | 
				
			||||||
 | 
					    dim_augment_err = 0 | 
				
			||||||
 | 
					    N = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # linearize with jacobians | 
				
			||||||
 | 
					  F_sym = f_err_sym.jacobian(x_err_sym) | 
				
			||||||
 | 
					  for sym in x_err_sym: | 
				
			||||||
 | 
					    F_sym = F_sym.subs(sym, 0) | 
				
			||||||
 | 
					  for i in range(len(obs_eqs)): | 
				
			||||||
 | 
					    obs_eqs[i].append(obs_eqs[i][0].jacobian(x_sym)) | 
				
			||||||
 | 
					    if msckf and obs_eqs[i][1] in feature_track_kinds: | 
				
			||||||
 | 
					      obs_eqs[i].append(obs_eqs[i][0].jacobian(obs_eqs[i][2])) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      obs_eqs[i].append(None) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # collect sympy functions | 
				
			||||||
 | 
					  sympy_functions = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # error functions | 
				
			||||||
 | 
					  sympy_functions.append(('err_fun', err_eqs[0], [err_eqs[1], err_eqs[2]])) | 
				
			||||||
 | 
					  sympy_functions.append(('inv_err_fun', inv_err_eqs[0], [inv_err_eqs[1], inv_err_eqs[2]])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # H modifier for ESKF updates | 
				
			||||||
 | 
					  sympy_functions.append(('H_mod_fun', H_mod_sym, [x_sym])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # state propagation function | 
				
			||||||
 | 
					  sympy_functions.append(('f_fun', f_sym, [x_sym, dt_sym])) | 
				
			||||||
 | 
					  sympy_functions.append(('F_fun', F_sym, [x_sym, dt_sym])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # observation functions | 
				
			||||||
 | 
					  for h_sym, kind, ea_sym, H_sym, He_sym in obs_eqs: | 
				
			||||||
 | 
					    sympy_functions.append(('h_%d' % kind, h_sym, [x_sym, ea_sym])) | 
				
			||||||
 | 
					    sympy_functions.append(('H_%d' % kind, H_sym, [x_sym, ea_sym])) | 
				
			||||||
 | 
					    if msckf and kind in feature_track_kinds: | 
				
			||||||
 | 
					      sympy_functions.append(('He_%d' % kind, He_sym, [x_sym, ea_sym])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Generate and wrap all th c code | 
				
			||||||
 | 
					  header, code = sympy_into_c(sympy_functions) | 
				
			||||||
 | 
					  extra_header = "#define DIM %d\n" % dim_x | 
				
			||||||
 | 
					  extra_header += "#define EDIM %d\n" % dim_err | 
				
			||||||
 | 
					  extra_header += "#define MEDIM %d\n" % dim_main_err | 
				
			||||||
 | 
					  extra_header += "typedef void (*Hfun)(double *, double *, double *);\n" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extra_header += "\nvoid predict(double *x, double *P, double *Q, double dt);" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extra_post = "" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for h_sym, kind, ea_sym, H_sym, He_sym in obs_eqs: | 
				
			||||||
 | 
					    if msckf and kind in feature_track_kinds: | 
				
			||||||
 | 
					      He_str = 'He_%d' % kind | 
				
			||||||
 | 
					      # ea_dim = ea_sym.shape[0] | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      He_str = 'NULL' | 
				
			||||||
 | 
					      # ea_dim = 1 # not really dim of ea but makes c function work | 
				
			||||||
 | 
					    maha_thresh = chi2_ppf(0.95, int(h_sym.shape[0])) # mahalanobis distance for outlier detection | 
				
			||||||
 | 
					    maha_test = kind in maha_test_kinds | 
				
			||||||
 | 
					    extra_post += """ | 
				
			||||||
 | 
					      void update_%d(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) { | 
				
			||||||
 | 
					        update<%d,%d,%d>(in_x, in_P, h_%d, H_%d, %s, in_z, in_R, in_ea, MAHA_THRESH_%d); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    """ % (kind, h_sym.shape[0], 3, maha_test, kind, kind, He_str, kind) | 
				
			||||||
 | 
					    extra_header += "\nconst static double MAHA_THRESH_%d = %f;" % (kind, maha_thresh) | 
				
			||||||
 | 
					    extra_header += "\nvoid update_%d(double *, double *, double *, double *, double *);" % kind | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  code += "\n" + extra_header | 
				
			||||||
 | 
					  code += "\n" + open(os.path.join(EXTERNAL_PATH, "ekf_c.c")).read() | 
				
			||||||
 | 
					  code += "\n" + extra_post | 
				
			||||||
 | 
					  header += "\n" + extra_header | 
				
			||||||
 | 
					  compile_code(name, code, header, EXTERNAL_PATH) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EKF_sym(): | 
				
			||||||
 | 
					  def __init__(self, name, Q, x_initial, P_initial, dim_main, dim_main_err, | 
				
			||||||
 | 
					                     N=0, dim_augment=0, dim_augment_err=0, maha_test_kinds=[]): | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					    Generates process function and all | 
				
			||||||
 | 
					    observation functions for the kalman | 
				
			||||||
 | 
					    filter. | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					    if N > 0: | 
				
			||||||
 | 
					      self.msckf = True | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.msckf = False | 
				
			||||||
 | 
					    self.N = N | 
				
			||||||
 | 
					    self.dim_augment = dim_augment | 
				
			||||||
 | 
					    self.dim_augment_err = dim_augment_err | 
				
			||||||
 | 
					    self.dim_main = dim_main | 
				
			||||||
 | 
					    self.dim_main_err = dim_main_err | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # state | 
				
			||||||
 | 
					    x_initial = x_initial.reshape((-1, 1)) | 
				
			||||||
 | 
					    self.dim_x = x_initial.shape[0] | 
				
			||||||
 | 
					    self.dim_err = P_initial.shape[0] | 
				
			||||||
 | 
					    assert dim_main + dim_augment*N == self.dim_x | 
				
			||||||
 | 
					    assert dim_main_err + dim_augment_err*N == self.dim_err | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # kinds that should get mahalanobis distance | 
				
			||||||
 | 
					    # tested for outlier rejection | 
				
			||||||
 | 
					    self.maha_test_kinds = maha_test_kinds | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # process noise | 
				
			||||||
 | 
					    self.Q = Q | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # rewind stuff | 
				
			||||||
 | 
					    self.rewind_t = [] | 
				
			||||||
 | 
					    self.rewind_states = [] | 
				
			||||||
 | 
					    self.rewind_obscache = [] | 
				
			||||||
 | 
					    self.init_state(x_initial, P_initial, None) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ffi, lib = wrap_compiled(name, EXTERNAL_PATH) | 
				
			||||||
 | 
					    kinds, self.feature_track_kinds = [], [] | 
				
			||||||
 | 
					    for func in dir(lib): | 
				
			||||||
 | 
					      if func[:2] == 'h_': | 
				
			||||||
 | 
					        kinds.append(int(func[2:])) | 
				
			||||||
 | 
					      if func[:3] == 'He_': | 
				
			||||||
 | 
					        self.feature_track_kinds.append(int(func[3:])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wrap all the sympy functions | 
				
			||||||
 | 
					    def wrap_1lists(name): | 
				
			||||||
 | 
					      func = eval("lib.%s" % name, {"lib":lib}) | 
				
			||||||
 | 
					      def ret(lst1, out): | 
				
			||||||
 | 
					        func(ffi.cast("double *", lst1.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      return ret | 
				
			||||||
 | 
					    def wrap_2lists(name): | 
				
			||||||
 | 
					      func = eval("lib.%s" % name, {"lib":lib}) | 
				
			||||||
 | 
					      def ret(lst1, lst2, out): | 
				
			||||||
 | 
					        func(ffi.cast("double *", lst1.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", lst2.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      return ret | 
				
			||||||
 | 
					    def wrap_1list_1float(name): | 
				
			||||||
 | 
					      func = eval("lib.%s" % name, {"lib":lib}) | 
				
			||||||
 | 
					      def ret(lst1, fl, out): | 
				
			||||||
 | 
					        func(ffi.cast("double *", lst1.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double", fl), | 
				
			||||||
 | 
					          ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.f = wrap_1list_1float("f_fun") | 
				
			||||||
 | 
					    self.F = wrap_1list_1float("F_fun") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.err_function = wrap_2lists("err_fun") | 
				
			||||||
 | 
					    self.inv_err_function = wrap_2lists("inv_err_fun") | 
				
			||||||
 | 
					    self.H_mod = wrap_1lists("H_mod_fun") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.hs, self.Hs, self.Hes = {}, {}, {} | 
				
			||||||
 | 
					    for kind in kinds: | 
				
			||||||
 | 
					      self.hs[kind] = wrap_2lists("h_%d" % kind) | 
				
			||||||
 | 
					      self.Hs[kind] = wrap_2lists("H_%d" % kind) | 
				
			||||||
 | 
					      if self.msckf  and kind in self.feature_track_kinds: | 
				
			||||||
 | 
					        self.Hes[kind] = wrap_2lists("He_%d" % kind) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wrap the C++ predict function | 
				
			||||||
 | 
					    def _predict_blas(x, P, dt): | 
				
			||||||
 | 
					      lib.predict(ffi.cast("double *", x.ctypes.data), | 
				
			||||||
 | 
					                  ffi.cast("double *", P.ctypes.data), | 
				
			||||||
 | 
					                  ffi.cast("double *", self.Q.ctypes.data), | 
				
			||||||
 | 
					                  ffi.cast("double", dt)) | 
				
			||||||
 | 
					      return x, P | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wrap the C++ update function | 
				
			||||||
 | 
					    def fun_wrapper(f, kind): | 
				
			||||||
 | 
					      f = eval("lib.%s" % f, {"lib": lib}) | 
				
			||||||
 | 
					      def _update_inner_blas(x, P, z, R, extra_args): | 
				
			||||||
 | 
					        f(ffi.cast("double *", x.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", P.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", z.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", R.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", extra_args.ctypes.data)) | 
				
			||||||
 | 
					        if self.msckf and kind in self.feature_track_kinds: | 
				
			||||||
 | 
					          y = z[:-len(extra_args)] | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					          y = z | 
				
			||||||
 | 
					        return x, P, y | 
				
			||||||
 | 
					      return _update_inner_blas | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._updates = {} | 
				
			||||||
 | 
					    for kind in kinds: | 
				
			||||||
 | 
					      self._updates[kind] = fun_wrapper("update_%d" % kind, kind) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _update_blas(x, P, kind, z, R, extra_args=[]): | 
				
			||||||
 | 
					        return self._updates[kind](x, P, z, R, extra_args) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # assign the functions | 
				
			||||||
 | 
					    self._predict = _predict_blas | 
				
			||||||
 | 
					    #self._predict = self._predict_python | 
				
			||||||
 | 
					    self._update = _update_blas | 
				
			||||||
 | 
					    #self._update = self._update_python | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def init_state(self, state, covs, filter_time): | 
				
			||||||
 | 
					    self.x = np.array(state.reshape((-1, 1))).astype(np.float64) | 
				
			||||||
 | 
					    self.P = np.array(covs).astype(np.float64) | 
				
			||||||
 | 
					    self.filter_time = filter_time | 
				
			||||||
 | 
					    self.augment_times = [0]*self.N | 
				
			||||||
 | 
					    self.rewind_obscache = [] | 
				
			||||||
 | 
					    self.rewind_t = [] | 
				
			||||||
 | 
					    self.rewind_states = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def augment(self): | 
				
			||||||
 | 
					    # TODO this is not a generalized way of doing | 
				
			||||||
 | 
					    # this and implies that the augmented states | 
				
			||||||
 | 
					    # are simply the first (dim_augment_state) | 
				
			||||||
 | 
					    # elements of the main state. | 
				
			||||||
 | 
					    assert self.msckf | 
				
			||||||
 | 
					    d1 = self.dim_main | 
				
			||||||
 | 
					    d2 = self.dim_main_err | 
				
			||||||
 | 
					    d3 = self.dim_augment | 
				
			||||||
 | 
					    d4 = self.dim_augment_err | 
				
			||||||
 | 
					    # push through augmented states | 
				
			||||||
 | 
					    self.x[d1:-d3] = self.x[d1+d3:] | 
				
			||||||
 | 
					    self.x[-d3:] = self.x[:d3] | 
				
			||||||
 | 
					    assert self.x.shape == (self.dim_x, 1) | 
				
			||||||
 | 
					    # push through augmented covs | 
				
			||||||
 | 
					    assert self.P.shape == (self.dim_err, self.dim_err) | 
				
			||||||
 | 
					    P_reduced = self.P | 
				
			||||||
 | 
					    P_reduced = np.delete(P_reduced, np.s_[d2:d2+d4], axis=1) | 
				
			||||||
 | 
					    P_reduced = np.delete(P_reduced, np.s_[d2:d2+d4], axis=0) | 
				
			||||||
 | 
					    assert P_reduced.shape == (self.dim_err -d4, self.dim_err -d4) | 
				
			||||||
 | 
					    to_mult = np.zeros((self.dim_err, self.dim_err - d4)) | 
				
			||||||
 | 
					    to_mult[:-d4,:] = np.eye(self.dim_err - d4) | 
				
			||||||
 | 
					    to_mult[-d4:,:d4] = np.eye(d4) | 
				
			||||||
 | 
					    self.P = to_mult.dot(P_reduced.dot(to_mult.T)) | 
				
			||||||
 | 
					    self.augment_times = self.augment_times[1:] | 
				
			||||||
 | 
					    self.augment_times.append(self.filter_time) | 
				
			||||||
 | 
					    assert self.P.shape == (self.dim_err, self.dim_err) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def state(self): | 
				
			||||||
 | 
					    return np.array(self.x).flatten() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def covs(self): | 
				
			||||||
 | 
					    return self.P | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rewind(self, t): | 
				
			||||||
 | 
					    # find where we are rewinding to | 
				
			||||||
 | 
					    idx = bisect_right(self.rewind_t, t) | 
				
			||||||
 | 
					    assert self.rewind_t[idx-1] <= t | 
				
			||||||
 | 
					    assert self.rewind_t[idx] > t    # must be true, or rewind wouldn't be called | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # set the state to the time right before that | 
				
			||||||
 | 
					    self.filter_time = self.rewind_t[idx-1] | 
				
			||||||
 | 
					    self.x[:] = self.rewind_states[idx-1][0] | 
				
			||||||
 | 
					    self.P[:] = self.rewind_states[idx-1][1] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # return the observations we rewound over for fast forwarding | 
				
			||||||
 | 
					    ret = self.rewind_obscache[idx:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # throw away the old future | 
				
			||||||
 | 
					    # TODO: is this making a copy? | 
				
			||||||
 | 
					    self.rewind_t = self.rewind_t[:idx] | 
				
			||||||
 | 
					    self.rewind_states = self.rewind_states[:idx] | 
				
			||||||
 | 
					    self.rewind_obscache = self.rewind_obscache[:idx] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def checkpoint(self, obs): | 
				
			||||||
 | 
					    # push to rewinder | 
				
			||||||
 | 
					    self.rewind_t.append(self.filter_time) | 
				
			||||||
 | 
					    self.rewind_states.append((np.copy(self.x), np.copy(self.P))) | 
				
			||||||
 | 
					    self.rewind_obscache.append(obs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # only keep a certain number around | 
				
			||||||
 | 
					    REWIND_TO_KEEP = 512 | 
				
			||||||
 | 
					    self.rewind_t = self.rewind_t[-REWIND_TO_KEEP:] | 
				
			||||||
 | 
					    self.rewind_states = self.rewind_states[-REWIND_TO_KEEP:] | 
				
			||||||
 | 
					    self.rewind_obscache = self.rewind_obscache[-REWIND_TO_KEEP:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_batch(self, t, kind, z, R, extra_args=[[]], augment=False): | 
				
			||||||
 | 
					    # TODO handle rewinding at this level" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # rewind | 
				
			||||||
 | 
					    if self.filter_time is not None and t < self.filter_time: | 
				
			||||||
 | 
					      if len(self.rewind_t) == 0 or t < self.rewind_t[0] or t < self.rewind_t[-1] -1.0: | 
				
			||||||
 | 
					        print("observation too old at %.3f with filter at %.3f, ignoring" % (t, self.filter_time)) | 
				
			||||||
 | 
					        return None | 
				
			||||||
 | 
					      rewound = self.rewind(t) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      rewound = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = self._predict_and_update_batch(t, kind, z, R, extra_args, augment) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # optional fast forward | 
				
			||||||
 | 
					    for r in rewound: | 
				
			||||||
 | 
					      self._predict_and_update_batch(*r) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def _predict_and_update_batch(self, t, kind, z, R, extra_args, augment=False): | 
				
			||||||
 | 
					    """The main kalman filter function | 
				
			||||||
 | 
					    Predicts the state and then updates a batch of observations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dim_x: dimensionality of the state space | 
				
			||||||
 | 
					    dim_z: dimensionality of the observation and depends on kind | 
				
			||||||
 | 
					    n: number of observations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args: | 
				
			||||||
 | 
					      t                 (float): Time of observation | 
				
			||||||
 | 
					      kind                (int): Type of observation | 
				
			||||||
 | 
					      z         (vec [n,dim_z]): Measurements | 
				
			||||||
 | 
					      R  (mat [n,dim_z, dim_z]): Measurement Noise | 
				
			||||||
 | 
					      extra_args    (list, [n]): Values used in H computations | 
				
			||||||
 | 
					    """ | 
				
			||||||
 | 
					    # initialize time | 
				
			||||||
 | 
					    if self.filter_time is None: | 
				
			||||||
 | 
					      self.filter_time = t | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # predict | 
				
			||||||
 | 
					    dt = t - self.filter_time | 
				
			||||||
 | 
					    assert dt >= 0 | 
				
			||||||
 | 
					    self.x, self.P = self._predict(self.x, self.P, dt) | 
				
			||||||
 | 
					    self.filter_time = t | 
				
			||||||
 | 
					    xk_km1, Pk_km1 = np.copy(self.x).flatten(), np.copy(self.P) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # update batch | 
				
			||||||
 | 
					    y = [] | 
				
			||||||
 | 
					    for i in range(len(z)): | 
				
			||||||
 | 
					      # these are from the user, so we canonicalize them | 
				
			||||||
 | 
					      z_i = np.array(z[i], dtype=np.float64, order='F') | 
				
			||||||
 | 
					      R_i = np.array(R[i], dtype=np.float64, order='F') | 
				
			||||||
 | 
					      extra_args_i = np.array(extra_args[i], dtype=np.float64, order='F') | 
				
			||||||
 | 
					      # update | 
				
			||||||
 | 
					      self.x, self.P, y_i = self._update(self.x, self.P, kind, z_i, R_i, extra_args=extra_args_i) | 
				
			||||||
 | 
					      y.append(y_i) | 
				
			||||||
 | 
					    xk_k, Pk_k = np.copy(self.x).flatten(), np.copy(self.P) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if augment: | 
				
			||||||
 | 
					      self.augment() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # checkpoint | 
				
			||||||
 | 
					    self.checkpoint((t, kind, z, R, extra_args)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return xk_km1, xk_k, Pk_km1, Pk_k, t, kind, y, z, extra_args | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def _predict_python(self, x, P, dt): | 
				
			||||||
 | 
					    x_new = np.zeros(x.shape, dtype=np.float64) | 
				
			||||||
 | 
					    self.f(x, dt, x_new) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    F = np.zeros(P.shape, dtype=np.float64) | 
				
			||||||
 | 
					    self.F(x, dt, F) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not self.msckf: | 
				
			||||||
 | 
					      P = dot(dot(F, P), F.T) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      # Update the predicted state covariance: | 
				
			||||||
 | 
					      #  Pk+1|k   =  |F*Pii*FT + Q*dt   F*Pij | | 
				
			||||||
 | 
					      #              |PijT*FT           Pjj   | | 
				
			||||||
 | 
					      # Where F is the jacobian of the main state | 
				
			||||||
 | 
					      # predict function, Pii is the main state's | 
				
			||||||
 | 
					      # covariance and Q its process noise. Pij | 
				
			||||||
 | 
					      # is the covariance between the augmented | 
				
			||||||
 | 
					      # states and the main state. | 
				
			||||||
 | 
					      # | 
				
			||||||
 | 
					      d2 = self.dim_main_err    # known at compile time | 
				
			||||||
 | 
					      F_curr = F[:d2, :d2] | 
				
			||||||
 | 
					      P[:d2, :d2] = (F_curr.dot(P[:d2, :d2])).dot(F_curr.T) | 
				
			||||||
 | 
					      P[:d2, d2:] = F_curr.dot(P[:d2, d2:]) | 
				
			||||||
 | 
					      P[d2:, :d2] = P[d2:, :d2].dot(F_curr.T) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    P += dt*self.Q | 
				
			||||||
 | 
					    return x_new, P | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def _update_python(self, x, P, kind, z, R, extra_args=[]): | 
				
			||||||
 | 
					    # init vars | 
				
			||||||
 | 
					    z = z.reshape((-1, 1)) | 
				
			||||||
 | 
					    h = np.zeros(z.shape, dtype=np.float64) | 
				
			||||||
 | 
					    H = np.zeros((z.shape[0], self.dim_x), dtype=np.float64) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # C functions | 
				
			||||||
 | 
					    self.hs[kind](x, extra_args, h) | 
				
			||||||
 | 
					    self.Hs[kind](x, extra_args, H) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # y is the "loss" | 
				
			||||||
 | 
					    y = z - h | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # *** same above this line *** | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if self.msckf and kind in self.Hes: | 
				
			||||||
 | 
					      # Do some algebraic magic to decorrelate | 
				
			||||||
 | 
					      He = np.zeros((z.shape[0], len(extra_args)), dtype=np.float64) | 
				
			||||||
 | 
					      self.Hes[kind](x, extra_args, He) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # TODO: Don't call a function here, do projection locally | 
				
			||||||
 | 
					      A = null(He.T) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      y = A.T.dot(y) | 
				
			||||||
 | 
					      H = A.T.dot(H) | 
				
			||||||
 | 
					      R = A.T.dot(R.dot(A)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # TODO If nullspace isn't the dimension we want | 
				
			||||||
 | 
					      if A.shape[1] + He.shape[1] != A.shape[0]: | 
				
			||||||
 | 
					        print('Warning: null space projection failed, measurement ignored') | 
				
			||||||
 | 
					        return x, P, np.zeros(A.shape[0] - He.shape[1]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # if using eskf | 
				
			||||||
 | 
					    H_mod = np.zeros((x.shape[0], P.shape[0]), dtype=np.float64) | 
				
			||||||
 | 
					    self.H_mod(x, H_mod) | 
				
			||||||
 | 
					    H = H.dot(H_mod) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Do mahalobis distance test | 
				
			||||||
 | 
					    # currently just runs on msckf observations | 
				
			||||||
 | 
					    # could run on anything if needed | 
				
			||||||
 | 
					    if self.msckf and kind in self.maha_test_kinds: | 
				
			||||||
 | 
					      a = np.linalg.inv(H.dot(P).dot(H.T) + R) | 
				
			||||||
 | 
					      maha_dist = y.T.dot(a.dot(y)) | 
				
			||||||
 | 
					      if maha_dist > chi2_ppf(0.95, y.shape[0]): | 
				
			||||||
 | 
					        R = 10e16*R | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # *** same below this line *** | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Outlier resilient weighting as described in: | 
				
			||||||
 | 
					    # "A Kalman Filter for Robust Outlier Detection - Jo-Anne Ting, ..." | 
				
			||||||
 | 
					    weight = 1 #(1.5)/(1 + np.sum(y**2)/np.sum(R)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    S = dot(dot(H, P), H.T) + R/weight | 
				
			||||||
 | 
					    K = solve(S, dot(H, P.T)).T | 
				
			||||||
 | 
					    I_KH = np.eye(P.shape[0]) - dot(K, H) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # update actual state | 
				
			||||||
 | 
					    delta_x = dot(K, y) | 
				
			||||||
 | 
					    P = dot(dot(I_KH, P), I_KH.T) + dot(dot(K, R), K.T) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # inject observed error into state | 
				
			||||||
 | 
					    x_new = np.zeros(x.shape, dtype=np.float64) | 
				
			||||||
 | 
					    self.err_function(x, delta_x, x_new) | 
				
			||||||
 | 
					    return x_new, P, y.flatten() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def maha_test(self, x, P, kind, z, R, extra_args=[], maha_thresh=0.95): | 
				
			||||||
 | 
					    # init vars | 
				
			||||||
 | 
					    z = z.reshape((-1, 1)) | 
				
			||||||
 | 
					    h = np.zeros(z.shape, dtype=np.float64) | 
				
			||||||
 | 
					    H = np.zeros((z.shape[0], self.dim_x), dtype=np.float64) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # C functions | 
				
			||||||
 | 
					    self.hs[kind](x, extra_args, h) | 
				
			||||||
 | 
					    self.Hs[kind](x, extra_args, H) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # y is the "loss" | 
				
			||||||
 | 
					    y = z - h | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # if using eskf | 
				
			||||||
 | 
					    H_mod = np.zeros((x.shape[0], P.shape[0]), dtype=np.float64) | 
				
			||||||
 | 
					    self.H_mod(x, H_mod) | 
				
			||||||
 | 
					    H = H.dot(H_mod) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    a = np.linalg.inv(H.dot(P).dot(H.T) + R) | 
				
			||||||
 | 
					    maha_dist = y.T.dot(a.dot(y)) | 
				
			||||||
 | 
					    if maha_dist > chi2_ppf(maha_thresh, y.shape[0]): | 
				
			||||||
 | 
					      return False | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      return True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rts_smooth(self, estimates, norm_quats=False): | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					    Returns rts smoothed results of | 
				
			||||||
 | 
					    kalman filter estimates | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If the kalman state is augmented with | 
				
			||||||
 | 
					    old states only the main state is smoothed | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					    xk_n = estimates[-1][0] | 
				
			||||||
 | 
					    Pk_n = estimates[-1][2] | 
				
			||||||
 | 
					    Fk_1 = np.zeros(Pk_n.shape, dtype=np.float64) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    states_smoothed = [xk_n] | 
				
			||||||
 | 
					    covs_smoothed = [Pk_n] | 
				
			||||||
 | 
					    for k in range(len(estimates) - 2, -1, -1): | 
				
			||||||
 | 
					      xk1_n = xk_n | 
				
			||||||
 | 
					      if norm_quats: | 
				
			||||||
 | 
					        xk1_n[3:7] /= np.linalg.norm(xk1_n[3:7]) | 
				
			||||||
 | 
					      Pk1_n = Pk_n | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      xk1_k, _, Pk1_k, _, t2, _, _, _, _ = estimates[k + 1] | 
				
			||||||
 | 
					      _, xk_k, _, Pk_k, t1, _, _, _, _ = estimates[k] | 
				
			||||||
 | 
					      dt = t2 - t1 | 
				
			||||||
 | 
					      self.F(xk_k, dt, Fk_1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      d1 = self.dim_main | 
				
			||||||
 | 
					      d2 = self.dim_main_err | 
				
			||||||
 | 
					      Ck = np.linalg.solve(Pk1_k[:d2,:d2], Fk_1[:d2,:d2].dot(Pk_k[:d2,:d2].T)).T | 
				
			||||||
 | 
					      xk_n = xk_k | 
				
			||||||
 | 
					      delta_x = np.zeros((Pk_n.shape[0], 1), dtype=np.float64) | 
				
			||||||
 | 
					      self.inv_err_function(xk1_k, xk1_n, delta_x) | 
				
			||||||
 | 
					      delta_x[:d2] = Ck.dot(delta_x[:d2]) | 
				
			||||||
 | 
					      x_new = np.zeros((xk_n.shape[0], 1), dtype=np.float64) | 
				
			||||||
 | 
					      self.err_function(xk_k, delta_x, x_new) | 
				
			||||||
 | 
					      xk_n[:d1] = x_new[:d1,0] | 
				
			||||||
 | 
					      Pk_n = Pk_k | 
				
			||||||
 | 
					      Pk_n[:d2,:d2] = Pk_k[:d2,:d2] + Ck.dot(Pk1_n[:d2,:d2] - Pk1_k[:d2,:d2]).dot(Ck.T) | 
				
			||||||
 | 
					      states_smoothed.append(xk_n) | 
				
			||||||
 | 
					      covs_smoothed.append(Pk_n) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return np.flipud(np.vstack(states_smoothed)), np.stack(covs_smoothed, 0)[::-1] | 
				
			||||||
@ -0,0 +1,56 @@ | 
				
			|||||||
 | 
					bool sane(double track [K + 1][5]) { | 
				
			||||||
 | 
					  double diffs_x [K-1]; | 
				
			||||||
 | 
					  double diffs_y [K-1]; | 
				
			||||||
 | 
					  int i; | 
				
			||||||
 | 
					  for (i = 0; i < K-1; i++) { | 
				
			||||||
 | 
					    diffs_x[i] = fabs(track[i+2][2] - track[i+1][2]); | 
				
			||||||
 | 
					    diffs_y[i] = fabs(track[i+2][3] - track[i+1][3]); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  for (i = 1; i < K-1; i++) { | 
				
			||||||
 | 
					    if (((diffs_x[i] > 0.05 or diffs_x[i-1] > 0.05) and | 
				
			||||||
 | 
					         (diffs_x[i] > 2*diffs_x[i-1] or | 
				
			||||||
 | 
					          diffs_x[i] < .5*diffs_x[i-1])) or | 
				
			||||||
 | 
					        ((diffs_y[i] > 0.05 or diffs_y[i-1] > 0.05) and | 
				
			||||||
 | 
						 (diffs_y[i] > 2*diffs_y[i-1] or | 
				
			||||||
 | 
					          diffs_y[i] < .5*diffs_y[i-1]))){ | 
				
			||||||
 | 
					      return false; | 
				
			||||||
 | 
					    } 
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void merge_features(double *tracks, double *features, long long *empty_idxs) { | 
				
			||||||
 | 
					  double feature_arr [3000][5]; | 
				
			||||||
 | 
					  memcpy(feature_arr, features, 3000 * 5 * sizeof(double)); | 
				
			||||||
 | 
					  double track_arr [6000][K + 1][5]; | 
				
			||||||
 | 
					  memcpy(track_arr, tracks, (K+1) * 6000 * 5 * sizeof(double)); | 
				
			||||||
 | 
					  int match; | 
				
			||||||
 | 
					  int empty_idx = 0; | 
				
			||||||
 | 
					  int idx; | 
				
			||||||
 | 
					  for (int i = 0; i < 3000; i++) { | 
				
			||||||
 | 
					    match = feature_arr[i][4]; | 
				
			||||||
 | 
					    if (track_arr[match][0][1] == match and track_arr[match][0][2] == 0){ | 
				
			||||||
 | 
					      track_arr[match][0][0] = track_arr[match][0][0] + 1; | 
				
			||||||
 | 
					      track_arr[match][0][1] = feature_arr[i][1]; | 
				
			||||||
 | 
					      track_arr[match][0][2] = 1; | 
				
			||||||
 | 
					      idx = track_arr[match][0][0]; | 
				
			||||||
 | 
					      memcpy(track_arr[match][idx], feature_arr[i], 5 * sizeof(double)); | 
				
			||||||
 | 
					      if (idx == K){ | 
				
			||||||
 | 
					        // label complete
 | 
				
			||||||
 | 
					        track_arr[match][0][3] = 1; | 
				
			||||||
 | 
						if (sane(track_arr[match])){ | 
				
			||||||
 | 
					          // label valid
 | 
				
			||||||
 | 
					          track_arr[match][0][4] = 1; 
 | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					      }		
 | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      // gen new track with this feature
 | 
				
			||||||
 | 
					      track_arr[empty_idxs[empty_idx]][0][0] = 1; | 
				
			||||||
 | 
					      track_arr[empty_idxs[empty_idx]][0][1] = feature_arr[i][1]; | 
				
			||||||
 | 
					      track_arr[empty_idxs[empty_idx]][0][2] = 1; | 
				
			||||||
 | 
					      memcpy(track_arr[empty_idxs[empty_idx]][1], feature_arr[i], 5 * sizeof(double)); | 
				
			||||||
 | 
					      empty_idx = empty_idx + 1; | 
				
			||||||
 | 
					    } 
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  memcpy(tracks, track_arr, (K+1) * 6000 * 5 * sizeof(double)); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,323 @@ | 
				
			|||||||
 | 
					import common.transformations.orientation as orient | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import scipy.optimize as opt | 
				
			||||||
 | 
					import time | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					from bisect import bisect_left | 
				
			||||||
 | 
					from common.sympy_helpers import sympy_into_c, quat_matrix_l | 
				
			||||||
 | 
					from common.ffi_wrapper import ffi_wrap, wrap_compiled, compile_code | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXTERNAL_PATH = os.path.dirname(os.path.abspath(__file__)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sane(track): | 
				
			||||||
 | 
					  img_pos = track[1:,2:4] | 
				
			||||||
 | 
					  diffs_x = abs(img_pos[1:,0] - img_pos[:-1,0]) | 
				
			||||||
 | 
					  diffs_y = abs(img_pos[1:,1] - img_pos[:-1,1]) | 
				
			||||||
 | 
					  for i in range(1, len(diffs_x)): | 
				
			||||||
 | 
					    if ((diffs_x[i] > 0.05 or diffs_x[i-1] > 0.05) and \ | 
				
			||||||
 | 
					       (diffs_x[i] > 2*diffs_x[i-1] or \ | 
				
			||||||
 | 
					        diffs_x[i] < .5*diffs_x[i-1])) or \ | 
				
			||||||
 | 
					       ((diffs_y[i] > 0.05 or diffs_y[i-1] > 0.05) and \ | 
				
			||||||
 | 
					       (diffs_y[i] > 2*diffs_y[i-1] or \ | 
				
			||||||
 | 
					        diffs_y[i] < .5*diffs_y[i-1])): | 
				
			||||||
 | 
					      return False | 
				
			||||||
 | 
					  return True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FeatureHandler(): | 
				
			||||||
 | 
					  def __init__(self, K): | 
				
			||||||
 | 
					    self.MAX_TRACKS=6000 | 
				
			||||||
 | 
					    self.K = K | 
				
			||||||
 | 
					    #Array of tracks, each track | 
				
			||||||
 | 
					    #has K 5D features preceded | 
				
			||||||
 | 
					    #by 5 params that inidicate | 
				
			||||||
 | 
					    #[f_idx, last_idx, updated, complete, valid] | 
				
			||||||
 | 
					    # f_idx: idx of current last feature in track | 
				
			||||||
 | 
					    # idx of of last feature in frame | 
				
			||||||
 | 
					    # bool for whether this track has been update | 
				
			||||||
 | 
					    # bool for whether this track is complete | 
				
			||||||
 | 
					    # bool for whether this track is valid | 
				
			||||||
 | 
					    self.tracks = np.zeros((self.MAX_TRACKS, K+1, 5)) | 
				
			||||||
 | 
					    self.tracks[:] = np.nan | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Wrap c code for slow matching | 
				
			||||||
 | 
					    c_header = "\nvoid merge_features(double *tracks, double *features, long long *empty_idxs);" | 
				
			||||||
 | 
					    c_code = "#define K %d\n" % K | 
				
			||||||
 | 
					    c_code += "\n" + open(os.path.join(EXTERNAL_PATH, "feature_handler.c")).read() | 
				
			||||||
 | 
					    ffi, lib = ffi_wrap('feature_handler', c_code, c_header) | 
				
			||||||
 | 
					    def merge_features_c(tracks, features, empty_idxs): | 
				
			||||||
 | 
					      lib.merge_features(ffi.cast("double *", tracks.ctypes.data), | 
				
			||||||
 | 
					                    ffi.cast("double *", features.ctypes.data), | 
				
			||||||
 | 
					                    ffi.cast("long long *", empty_idxs.ctypes.data)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #self.merge_features = self.merge_features_python | 
				
			||||||
 | 
					    self.merge_features = merge_features_c | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def reset(self): | 
				
			||||||
 | 
					    self.tracks[:] = np.nan | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def merge_features_python(self, tracks, features, empty_idxs): | 
				
			||||||
 | 
					    empty_idx = 0 | 
				
			||||||
 | 
					    for f in features: | 
				
			||||||
 | 
					      match_idx = int(f[4]) | 
				
			||||||
 | 
					      if tracks[match_idx, 0, 1] == match_idx and tracks[match_idx, 0 ,2] == 0: | 
				
			||||||
 | 
					        tracks[match_idx, 0, 0] += 1 | 
				
			||||||
 | 
					        tracks[match_idx, 0, 1] = f[1] | 
				
			||||||
 | 
					        tracks[match_idx, 0, 2] = 1 | 
				
			||||||
 | 
					        tracks[match_idx, int(tracks[match_idx, 0, 0])] = f | 
				
			||||||
 | 
					        if tracks[match_idx, 0, 0] == self.K: | 
				
			||||||
 | 
					          tracks[match_idx, 0, 3] = 1 | 
				
			||||||
 | 
					          if sane(tracks[match_idx]): | 
				
			||||||
 | 
					            tracks[match_idx, 0, 4] = 1 | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        if empty_idx == len(empty_idxs): | 
				
			||||||
 | 
					          print('need more empty space') | 
				
			||||||
 | 
					          continue | 
				
			||||||
 | 
					        tracks[empty_idxs[empty_idx], 0, 0] = 1 | 
				
			||||||
 | 
					        tracks[empty_idxs[empty_idx], 0, 1] = f[1] | 
				
			||||||
 | 
					        tracks[empty_idxs[empty_idx], 0, 2] = 1 | 
				
			||||||
 | 
					        tracks[empty_idxs[empty_idx], 1] = f | 
				
			||||||
 | 
					        empty_idx += 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update_tracks(self, features): | 
				
			||||||
 | 
					    t0 = time.time() | 
				
			||||||
 | 
					    last_idxs = np.copy(self.tracks[:,0,1]) | 
				
			||||||
 | 
					    real = np.isfinite(last_idxs) | 
				
			||||||
 | 
					    self.tracks[last_idxs[real].astype(int)] = self.tracks[real] | 
				
			||||||
 | 
					    mask = np.ones(self.MAX_TRACKS, np.bool) | 
				
			||||||
 | 
					    mask[last_idxs[real].astype(int)] = 0 | 
				
			||||||
 | 
					    empty_idxs = np.arange(self.MAX_TRACKS)[mask] | 
				
			||||||
 | 
					    self.tracks[empty_idxs] = np.nan | 
				
			||||||
 | 
					    self.tracks[:,0,2] = 0 | 
				
			||||||
 | 
					    self.merge_features(self.tracks, features, empty_idxs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_features(self, features): | 
				
			||||||
 | 
					    self.update_tracks(features) | 
				
			||||||
 | 
					    valid_idxs = self.tracks[:,0,4] == 1 | 
				
			||||||
 | 
					    complete_idxs = self.tracks[:,0,3] == 1 | 
				
			||||||
 | 
					    stale_idxs = self.tracks[:,0,2] == 0 | 
				
			||||||
 | 
					    valid_tracks = self.tracks[valid_idxs] | 
				
			||||||
 | 
					    self.tracks[complete_idxs] = np.nan | 
				
			||||||
 | 
					    self.tracks[stale_idxs] = np.nan | 
				
			||||||
 | 
					    return valid_tracks[:,1:,:4].reshape((len(valid_tracks), self.K*4)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_residual(K): | 
				
			||||||
 | 
					  import sympy as sp | 
				
			||||||
 | 
					  from common.sympy_helpers import quat_rotate | 
				
			||||||
 | 
					  x_sym = sp.MatrixSymbol('abr', 3,1) | 
				
			||||||
 | 
					  poses_sym = sp.MatrixSymbol('poses', 7*K,1) | 
				
			||||||
 | 
					  img_pos_sym = sp.MatrixSymbol('img_positions', 2*K,1) | 
				
			||||||
 | 
					  alpha, beta, rho = x_sym | 
				
			||||||
 | 
					  to_c = sp.Matrix(orient.rot_matrix(-np.pi/2, -np.pi/2, 0)) | 
				
			||||||
 | 
					  pos_0 = sp.Matrix(np.array(poses_sym[K*7-7:K*7-4])[:,0]) | 
				
			||||||
 | 
					  q = poses_sym[K*7-4:K*7] | 
				
			||||||
 | 
					  quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					  rot_g_to_0 = to_c*quat_rot.T | 
				
			||||||
 | 
					  rows = [] | 
				
			||||||
 | 
					  for i in range(K): | 
				
			||||||
 | 
					    pos_i = sp.Matrix(np.array(poses_sym[i*7:i*7+3])[:,0]) | 
				
			||||||
 | 
					    q = poses_sym[7*i+3:7*i+7] | 
				
			||||||
 | 
					    quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					    rot_g_to_i = to_c*quat_rot.T | 
				
			||||||
 | 
					    rot_0_to_i = rot_g_to_i*(rot_g_to_0.T) | 
				
			||||||
 | 
					    trans_0_to_i = rot_g_to_i*(pos_0 - pos_i) | 
				
			||||||
 | 
					    funct_vec = rot_0_to_i*sp.Matrix([alpha, beta, 1]) + rho*trans_0_to_i | 
				
			||||||
 | 
					    h1, h2, h3 = funct_vec | 
				
			||||||
 | 
					    rows.append(h1/h3 - img_pos_sym[i*2 +0]) | 
				
			||||||
 | 
					    rows.append(h2/h3 - img_pos_sym[i*2 + 1]) | 
				
			||||||
 | 
					  img_pos_residual_sym = sp.Matrix(rows) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # sympy into c | 
				
			||||||
 | 
					  sympy_functions = [] | 
				
			||||||
 | 
					  sympy_functions.append(('res_fun', img_pos_residual_sym, [x_sym, poses_sym, img_pos_sym])) | 
				
			||||||
 | 
					  sympy_functions.append(('jac_fun', img_pos_residual_sym.jacobian(x_sym), [x_sym, poses_sym, img_pos_sym])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return sympy_functions | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_orient_error_jac(K): | 
				
			||||||
 | 
					  import sympy as sp | 
				
			||||||
 | 
					  from common.sympy_helpers import quat_rotate | 
				
			||||||
 | 
					  x_sym = sp.MatrixSymbol('abr', 3,1) | 
				
			||||||
 | 
					  dtheta = sp.MatrixSymbol('dtheta', 3,1) | 
				
			||||||
 | 
					  delta_quat = sp.Matrix(np.ones(4)) | 
				
			||||||
 | 
					  delta_quat[1:,:] = sp.Matrix(0.5*dtheta[0:3,:]) | 
				
			||||||
 | 
					  poses_sym = sp.MatrixSymbol('poses', 7*K,1) | 
				
			||||||
 | 
					  img_pos_sym = sp.MatrixSymbol('img_positions', 2*K,1) | 
				
			||||||
 | 
					  alpha, beta, rho = x_sym | 
				
			||||||
 | 
					  to_c = sp.Matrix(orient.rot_matrix(-np.pi/2, -np.pi/2, 0)) | 
				
			||||||
 | 
					  pos_0 = sp.Matrix(np.array(poses_sym[K*7-7:K*7-4])[:,0]) | 
				
			||||||
 | 
					  q = quat_matrix_l(poses_sym[K*7-4:K*7])*delta_quat | 
				
			||||||
 | 
					  quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					  rot_g_to_0 = to_c*quat_rot.T | 
				
			||||||
 | 
					  rows = [] | 
				
			||||||
 | 
					  for i in range(K): | 
				
			||||||
 | 
					    pos_i = sp.Matrix(np.array(poses_sym[i*7:i*7+3])[:,0]) | 
				
			||||||
 | 
					    q = quat_matrix_l(poses_sym[7*i+3:7*i+7])*delta_quat | 
				
			||||||
 | 
					    quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					    rot_g_to_i = to_c*quat_rot.T | 
				
			||||||
 | 
					    rot_0_to_i = rot_g_to_i*(rot_g_to_0.T) | 
				
			||||||
 | 
					    trans_0_to_i = rot_g_to_i*(pos_0 - pos_i) | 
				
			||||||
 | 
					    funct_vec = rot_0_to_i*sp.Matrix([alpha, beta, 1]) + rho*trans_0_to_i | 
				
			||||||
 | 
					    h1, h2, h3 = funct_vec | 
				
			||||||
 | 
					    rows.append(h1/h3 - img_pos_sym[i*2 +0]) | 
				
			||||||
 | 
					    rows.append(h2/h3 - img_pos_sym[i*2 + 1]) | 
				
			||||||
 | 
					  img_pos_residual_sym = sp.Matrix(rows) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # sympy into c | 
				
			||||||
 | 
					  sympy_functions = [] | 
				
			||||||
 | 
					  sympy_functions.append(('orient_error_jac', img_pos_residual_sym.jacobian(dtheta), [x_sym, poses_sym, img_pos_sym, dtheta])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return sympy_functions | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LstSqComputer(): | 
				
			||||||
 | 
					  def __init__(self, K, MIN_DEPTH=2, MAX_DEPTH=500, debug=False): | 
				
			||||||
 | 
					    self.to_c = orient.rot_matrix(-np.pi/2, -np.pi/2, 0) | 
				
			||||||
 | 
					    self.MAX_DEPTH = MAX_DEPTH | 
				
			||||||
 | 
					    self.MIN_DEPTH = MIN_DEPTH | 
				
			||||||
 | 
					    self.debug = debug | 
				
			||||||
 | 
					    self.name = 'pos_computer_' + str(K) | 
				
			||||||
 | 
					    if debug: | 
				
			||||||
 | 
					      self.name += '_debug' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      dir_path = os.path.dirname(__file__) | 
				
			||||||
 | 
					      deps = [dir_path + '/' + 'feature_handler.py', | 
				
			||||||
 | 
					              dir_path + '/' + 'compute_pos.c'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      outs = [dir_path + '/' + self.name + '.o', | 
				
			||||||
 | 
					              dir_path + '/' + self.name + '.so', | 
				
			||||||
 | 
					              dir_path + '/' + self.name + '.cpp'] | 
				
			||||||
 | 
					      out_times = list(map(os.path.getmtime, outs)) | 
				
			||||||
 | 
					      dep_times = list(map(os.path.getmtime, deps)) | 
				
			||||||
 | 
					      rebuild = os.getenv("REBUILD", False) | 
				
			||||||
 | 
					      if min(out_times) < max(dep_times) or rebuild: | 
				
			||||||
 | 
					        list(map(os.remove, outs)) | 
				
			||||||
 | 
					        # raise the OSError if removing didnt | 
				
			||||||
 | 
					        # raise one to start the compilation | 
				
			||||||
 | 
					        raise OSError() | 
				
			||||||
 | 
					    except OSError as e: | 
				
			||||||
 | 
					      # gen c code for sympy functions | 
				
			||||||
 | 
					      sympy_functions = generate_residual(K) | 
				
			||||||
 | 
					      #if debug: | 
				
			||||||
 | 
					      #  sympy_functions.extend(generate_orient_error_jac(K)) | 
				
			||||||
 | 
					      header, code = sympy_into_c(sympy_functions) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # ffi wrap c code | 
				
			||||||
 | 
					      extra_header = "\nvoid compute_pos(double *to_c, double *in_poses, double *in_img_positions, double *param, double *pos);" | 
				
			||||||
 | 
					      code += "\n#define KDIM %d\n" % K | 
				
			||||||
 | 
					      header += "\n" + extra_header | 
				
			||||||
 | 
					      code += "\n" + open(os.path.join(EXTERNAL_PATH, "compute_pos.c")).read() | 
				
			||||||
 | 
					      compile_code(self.name, code, header, EXTERNAL_PATH) | 
				
			||||||
 | 
					    ffi, lib = wrap_compiled(self.name, EXTERNAL_PATH) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wrap c functions | 
				
			||||||
 | 
					    #if debug: | 
				
			||||||
 | 
					      #def orient_error_jac(x, poses, img_positions, dtheta): | 
				
			||||||
 | 
					      #  out = np.zeros(((K*2, 3)), dtype=np.float64) | 
				
			||||||
 | 
					      #  lib.orient_error_jac(ffi.cast("double *", x.ctypes.data), | 
				
			||||||
 | 
					      #    ffi.cast("double *", poses.ctypes.data), | 
				
			||||||
 | 
					      #    ffi.cast("double *", img_positions.ctypes.data), | 
				
			||||||
 | 
					      #    ffi.cast("double *", dtheta.ctypes.data), | 
				
			||||||
 | 
					      #    ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      #  return out | 
				
			||||||
 | 
					      #self.orient_error_jac = orient_error_jac | 
				
			||||||
 | 
					    def residual_jac(x, poses, img_positions): | 
				
			||||||
 | 
					      out = np.zeros(((K*2, 3)), dtype=np.float64) | 
				
			||||||
 | 
					      lib.jac_fun(ffi.cast("double *", x.ctypes.data), | 
				
			||||||
 | 
					        ffi.cast("double *", poses.ctypes.data), | 
				
			||||||
 | 
					        ffi.cast("double *", img_positions.ctypes.data), | 
				
			||||||
 | 
					        ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      return out | 
				
			||||||
 | 
					    def residual(x, poses, img_positions): | 
				
			||||||
 | 
					      out = np.zeros((K*2), dtype=np.float64) | 
				
			||||||
 | 
					      lib.res_fun(ffi.cast("double *", x.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", poses.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", img_positions.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", out.ctypes.data)) | 
				
			||||||
 | 
					      return out | 
				
			||||||
 | 
					    self.residual = residual | 
				
			||||||
 | 
					    self.residual_jac = residual_jac | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def compute_pos_c(poses, img_positions): | 
				
			||||||
 | 
					      pos = np.zeros(3, dtype=np.float64) | 
				
			||||||
 | 
					      param = np.zeros(3, dtype=np.float64) | 
				
			||||||
 | 
					      # Can't be a view for the ctype | 
				
			||||||
 | 
					      img_positions = np.copy(img_positions) | 
				
			||||||
 | 
					      lib.compute_pos(ffi.cast("double *", self.to_c.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", poses.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", img_positions.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", param.ctypes.data), | 
				
			||||||
 | 
					          ffi.cast("double *", pos.ctypes.data)) | 
				
			||||||
 | 
					      return pos, param | 
				
			||||||
 | 
					    self.compute_pos_c = compute_pos_c | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def compute_pos(self, poses, img_positions, debug=False): | 
				
			||||||
 | 
					    pos, param =  self.compute_pos_c(poses, img_positions) | 
				
			||||||
 | 
					    #pos, param =  self.compute_pos_python(poses, img_positions) | 
				
			||||||
 | 
					    depth = 1/param[2] | 
				
			||||||
 | 
					    if debug: | 
				
			||||||
 | 
					      if not self.debug: | 
				
			||||||
 | 
					        raise NotImplementedError("This is not a debug computer") | 
				
			||||||
 | 
					      #orient_err_jac = self.orient_error_jac(param, poses, img_positions, np.zeros(3)).reshape((-1,2,3)) | 
				
			||||||
 | 
					      jac = self.residual_jac(param, poses, img_positions).reshape((-1,2,3)) | 
				
			||||||
 | 
					      res = self.residual(param, poses, img_positions).reshape((-1,2)) | 
				
			||||||
 | 
					      return pos, param, res, jac #, orient_err_jac | 
				
			||||||
 | 
					    elif (self.MIN_DEPTH < depth < self.MAX_DEPTH): | 
				
			||||||
 | 
					      return pos | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      return None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def gauss_newton(self, fun, jac, x, args): | 
				
			||||||
 | 
					    poses, img_positions = args | 
				
			||||||
 | 
					    delta = 1 | 
				
			||||||
 | 
					    counter = 0 | 
				
			||||||
 | 
					    while abs(np.linalg.norm(delta)) > 1e-4 and counter < 30: | 
				
			||||||
 | 
					      delta = np.linalg.pinv(jac(x, poses, img_positions)).dot(fun(x, poses, img_positions)) | 
				
			||||||
 | 
					      x = x - delta | 
				
			||||||
 | 
					      counter += 1 | 
				
			||||||
 | 
					    return [x] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def compute_pos_python(self, poses, img_positions, check_quality=False): | 
				
			||||||
 | 
					    # This procedure is also described | 
				
			||||||
 | 
					    # in the MSCKF paper (Mourikis et al. 2007) | 
				
			||||||
 | 
					    x = np.array([img_positions[-1][0], | 
				
			||||||
 | 
					                  img_positions[-1][1], 0.1]) | 
				
			||||||
 | 
					    res = opt.leastsq(self.residual, x, Dfun=self.residual_jac, args=(poses, img_positions)) # scipy opt | 
				
			||||||
 | 
					    #res = self.gauss_newton(self.residual, self.residual_jac, x, (poses, img_positions)) # diy gauss_newton | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    alpha, beta, rho = res[0] | 
				
			||||||
 | 
					    rot_0_to_g = (orient.rotations_from_quats(poses[-1,3:])).dot(self.to_c.T) | 
				
			||||||
 | 
					    return (rot_0_to_g.dot(np.array([alpha, beta, 1])))/rho + poses[-1,:3] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					''' | 
				
			||||||
 | 
					EXPERIMENTAL CODE | 
				
			||||||
 | 
					''' | 
				
			||||||
 | 
					def unroll_shutter(img_positions, poses, v, rot_rates, ecef_pos): | 
				
			||||||
 | 
					  # only speed correction for now | 
				
			||||||
 | 
					  t_roll = 0.016 # 16ms rolling shutter? | 
				
			||||||
 | 
					  vroll, vpitch, vyaw = rot_rates | 
				
			||||||
 | 
					  A = 0.5*np.array([[-1, -vroll, -vpitch, -vyaw], | 
				
			||||||
 | 
					                 [vroll, 0, vyaw, -vpitch], | 
				
			||||||
 | 
					                 [vpitch, -vyaw, 0, vroll], | 
				
			||||||
 | 
					                 [vyaw, vpitch, -vroll, 0]]) | 
				
			||||||
 | 
					  q_dot = A.dot(poses[-1][3:7]) | 
				
			||||||
 | 
					  v = np.append(v, q_dot) | 
				
			||||||
 | 
					  v = np.array([v[0], v[1], v[2],0,0,0,0]) | 
				
			||||||
 | 
					  current_pose = poses[-1] + v*0.05 | 
				
			||||||
 | 
					  poses = np.vstack((current_pose, poses)) | 
				
			||||||
 | 
					  dt = -img_positions[:,1]*t_roll/0.48 | 
				
			||||||
 | 
					  errs = project(poses, ecef_pos) - project(poses + np.atleast_2d(dt).T.dot(np.atleast_2d(v)), ecef_pos) | 
				
			||||||
 | 
					  return img_positions - errs | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def project(poses, ecef_pos): | 
				
			||||||
 | 
					  img_positions = np.zeros((len(poses), 2)) | 
				
			||||||
 | 
					  for i, p in enumerate(poses): | 
				
			||||||
 | 
					    cam_frame = orient.rotations_from_quats(p[3:]).T.dot(ecef_pos - p[:3]) | 
				
			||||||
 | 
					    img_positions[i] = np.array([cam_frame[1]/cam_frame[0], cam_frame[2]/cam_frame[0]]) | 
				
			||||||
 | 
					  return img_positions | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,105 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from . import gnss_model | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from .ekf_sym import EKF_sym | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.loc_kf import parse_pr, parse_prr | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class States(): | 
				
			||||||
 | 
					  ECEF_POS = slice(0,3) # x, y and z in ECEF in meters | 
				
			||||||
 | 
					  ECEF_VELOCITY = slice(3,6) | 
				
			||||||
 | 
					  CLOCK_BIAS = slice(6, 7) # clock bias in light-meters, | 
				
			||||||
 | 
					  CLOCK_DRIFT = slice(7, 8) # clock drift in light-meters/s, | 
				
			||||||
 | 
					  CLOCK_ACCELERATION = slice(8, 9) # clock acceleration in light-meters/s**2 | 
				
			||||||
 | 
					  GLONASS_BIAS = slice(9, 10) # clock drift in light-meters/s, | 
				
			||||||
 | 
					  GLONASS_FREQ_SLOPE = slice(10, 11) # GLONASS bias in m expressed as bias + freq_num*freq_slope | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GNSSKalman(): | 
				
			||||||
 | 
					  def __init__(self, N=0, max_tracks=3000): | 
				
			||||||
 | 
					    x_initial = np.array([-2712700.6008, -4281600.6679, 3859300.1830, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # state covariance | 
				
			||||||
 | 
					    P_initial = np.diag([10000**2, 10000**2, 10000**2, | 
				
			||||||
 | 
					                         10**2, 10**2, 10**2, | 
				
			||||||
 | 
					                         (2000000)**2, (100)**2, (0.5)**2, | 
				
			||||||
 | 
					                         (10)**2, (1)**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # process noise | 
				
			||||||
 | 
					    Q = np.diag([0.3**2, 0.3**2, 0.3**2, | 
				
			||||||
 | 
					                 3**2, 3**2, 3**2, | 
				
			||||||
 | 
					                 (.1)**2, (0)**2, (0.01)**2, | 
				
			||||||
 | 
					                 .1**2, (.01)**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.dim_state = x_initial.shape[0] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # mahalanobis outlier rejection | 
				
			||||||
 | 
					    maha_test_kinds = []#ObservationKind.PSEUDORANGE_RATE, ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_GLONASS] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'gnss' | 
				
			||||||
 | 
					    gnss_model.gen_model(name, self.dim_state, maha_test_kinds) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # init filter | 
				
			||||||
 | 
					    self.filter = EKF_sym(name, Q, x_initial, P_initial, self.dim_state, self.dim_state, maha_test_kinds=maha_test_kinds) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def x(self): | 
				
			||||||
 | 
					    return self.filter.state() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def P(self): | 
				
			||||||
 | 
					    return self.filter.covs() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict(self, t): | 
				
			||||||
 | 
					    return self.filter.predict(t) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rts_smooth(self, estimates): | 
				
			||||||
 | 
					    return self.filter.rts_smooth(estimates, norm_quats=False) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def init_state(self, state, covs_diag=None, covs=None, filter_time=None): | 
				
			||||||
 | 
					    if covs_diag is not None: | 
				
			||||||
 | 
					      P = np.diag(covs_diag) | 
				
			||||||
 | 
					    elif covs is not None: | 
				
			||||||
 | 
					      P = covs | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      P = self.filter.covs() | 
				
			||||||
 | 
					    self.filter.init_state(state, P, filter_time) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_observe(self, t, kind, data): | 
				
			||||||
 | 
					    if len(data) > 0: | 
				
			||||||
 | 
					      data = np.atleast_2d(data) | 
				
			||||||
 | 
					    if kind == ObservationKind.PSEUDORANGE_GPS or kind == ObservationKind.PSEUDORANGE_GLONASS: | 
				
			||||||
 | 
					      r = self.predict_and_update_pseudorange(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.PSEUDORANGE_RATE_GPS or kind == ObservationKind.PSEUDORANGE_RATE_GLONASS: | 
				
			||||||
 | 
					      r = self.predict_and_update_pseudorange_rate(data, t, kind) | 
				
			||||||
 | 
					    return r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_pseudorange(self, meas, t, kind): | 
				
			||||||
 | 
					    R = np.zeros((len(meas), 1, 1)) | 
				
			||||||
 | 
					    sat_pos_freq = np.zeros((len(meas), 4)) | 
				
			||||||
 | 
					    z = np.zeros((len(meas), 1)) | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z_i, R_i, sat_pos_freq_i = parse_pr(m) | 
				
			||||||
 | 
					      sat_pos_freq[i,:] = sat_pos_freq_i | 
				
			||||||
 | 
					      z[i,:] = z_i | 
				
			||||||
 | 
					      R[i,:,:] = R_i | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_freq) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_pseudorange_rate(self, meas, t, kind): | 
				
			||||||
 | 
					    R = np.zeros((len(meas), 1, 1)) | 
				
			||||||
 | 
					    z = np.zeros((len(meas), 1)) | 
				
			||||||
 | 
					    sat_pos_vel = np.zeros((len(meas), 6)) | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z_i, R_i, sat_pos_vel_i = parse_prr(m) | 
				
			||||||
 | 
					      sat_pos_vel[i] = sat_pos_vel_i | 
				
			||||||
 | 
					      R[i,:,:] = R_i | 
				
			||||||
 | 
					      z[i, :] = z_i | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_vel) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  GNSSKalman() | 
				
			||||||
@ -0,0 +1,93 @@ | 
				
			|||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import sympy as sp | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					from .kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from .ekf_sym import gen_code | 
				
			||||||
 | 
					from common.sympy_helpers import cross, euler_rotate, quat_rotate, quat_matrix_l, quat_matrix_r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_model(name, dim_state, maha_test_kinds): | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # check if rebuild is needed | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    dir_path = os.path.dirname(__file__) | 
				
			||||||
 | 
					    deps = [dir_path + '/' + 'ekf_c.c', | 
				
			||||||
 | 
					            dir_path + '/' + 'ekf_sym.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'gnss_model.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'gnss_kf.py'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outs = [dir_path + '/' + name + '.o', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.so', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.cpp'] | 
				
			||||||
 | 
					    out_times = list(map(os.path.getmtime, outs)) | 
				
			||||||
 | 
					    dep_times = list(map(os.path.getmtime, deps)) | 
				
			||||||
 | 
					    rebuild = os.getenv("REBUILD", False) | 
				
			||||||
 | 
					    if min(out_times) > max(dep_times) and not rebuild: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    list(map(os.remove, outs)) | 
				
			||||||
 | 
					  except OSError as e: | 
				
			||||||
 | 
					    pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # make functions and jacobians with sympy | 
				
			||||||
 | 
					  # state variables | 
				
			||||||
 | 
					  state_sym = sp.MatrixSymbol('state', dim_state, 1) | 
				
			||||||
 | 
					  state = sp.Matrix(state_sym) | 
				
			||||||
 | 
					  x,y,z = state[0:3,:] | 
				
			||||||
 | 
					  v = state[3:6,:] | 
				
			||||||
 | 
					  vx, vy, vz = v | 
				
			||||||
 | 
					  cb, cd, ca = state[6:9,:] | 
				
			||||||
 | 
					  glonass_bias, glonass_freq_slope = state[9:11,:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dt = sp.Symbol('dt') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state_dot = sp.Matrix(np.zeros((dim_state, 1))) | 
				
			||||||
 | 
					  state_dot[:3,:] = v | 
				
			||||||
 | 
					  state_dot[6,0] = cd | 
				
			||||||
 | 
					  state_dot[7,0] = ca | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Basic descretization, 1st order intergrator | 
				
			||||||
 | 
					  # Can be pretty bad if dt is big | 
				
			||||||
 | 
					  f_sym = state + dt*state_dot | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					  # Observation functions | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # extra args | 
				
			||||||
 | 
					  sat_pos_freq_sym = sp.MatrixSymbol('sat_pos', 4, 1) | 
				
			||||||
 | 
					  sat_pos_vel_sym = sp.MatrixSymbol('sat_pos_vel', 6, 1) | 
				
			||||||
 | 
					  sat_los_sym = sp.MatrixSymbol('sat_los', 3, 1) | 
				
			||||||
 | 
					  orb_epos_sym = sp.MatrixSymbol('orb_epos_sym', 3, 1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # expand extra args | 
				
			||||||
 | 
					  sat_x, sat_y, sat_z, glonass_freq = sat_pos_freq_sym | 
				
			||||||
 | 
					  sat_vx, sat_vy, sat_vz = sat_pos_vel_sym[3:] | 
				
			||||||
 | 
					  los_x, los_y, los_z = sat_los_sym | 
				
			||||||
 | 
					  orb_x, orb_y, orb_z = orb_epos_sym | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_pseudorange_sym = sp.Matrix([sp.sqrt( | 
				
			||||||
 | 
					                                  (x - sat_x)**2 + | 
				
			||||||
 | 
					                                  (y - sat_y)**2 + | 
				
			||||||
 | 
					                                  (z - sat_z)**2) + | 
				
			||||||
 | 
					                                  cb]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_pseudorange_glonass_sym = sp.Matrix([sp.sqrt( | 
				
			||||||
 | 
					                                  (x - sat_x)**2 + | 
				
			||||||
 | 
					                                  (y - sat_y)**2 + | 
				
			||||||
 | 
					                                  (z - sat_z)**2) + | 
				
			||||||
 | 
					                                  cb + glonass_bias + glonass_freq_slope*glonass_freq]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  los_vector = (sp.Matrix(sat_pos_vel_sym[0:3]) - sp.Matrix([x, y, z])) | 
				
			||||||
 | 
					  los_vector = los_vector / sp.sqrt(los_vector[0]**2 + los_vector[1]**2 + los_vector[2]**2) | 
				
			||||||
 | 
					  h_pseudorange_rate_sym = sp.Matrix([los_vector[0]*(sat_vx - vx) + | 
				
			||||||
 | 
					                                         los_vector[1]*(sat_vy - vy) + | 
				
			||||||
 | 
					                                         los_vector[2]*(sat_vz - vz) + | 
				
			||||||
 | 
					                                         cd]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  obs_eqs = [[h_pseudorange_sym, ObservationKind.PSEUDORANGE_GPS, sat_pos_freq_sym], | 
				
			||||||
 | 
					             [h_pseudorange_glonass_sym, ObservationKind.PSEUDORANGE_GLONASS, sat_pos_freq_sym], | 
				
			||||||
 | 
					             [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GPS, sat_pos_vel_sym], | 
				
			||||||
 | 
					             [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GLONASS, sat_pos_vel_sym]] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gen_code(name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds) | 
				
			||||||
@ -0,0 +1,165 @@ | 
				
			|||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					from bisect import bisect | 
				
			||||||
 | 
					from tqdm import tqdm | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ObservationKind(): | 
				
			||||||
 | 
					  UNKNOWN = 0 | 
				
			||||||
 | 
					  NO_OBSERVATION = 1 | 
				
			||||||
 | 
					  GPS_NED = 2 | 
				
			||||||
 | 
					  ODOMETRIC_SPEED = 3 | 
				
			||||||
 | 
					  PHONE_GYRO = 4 | 
				
			||||||
 | 
					  GPS_VEL = 5 | 
				
			||||||
 | 
					  PSEUDORANGE_GPS = 6 | 
				
			||||||
 | 
					  PSEUDORANGE_RATE_GPS = 7 | 
				
			||||||
 | 
					  SPEED = 8 | 
				
			||||||
 | 
					  NO_ROT = 9 | 
				
			||||||
 | 
					  PHONE_ACCEL = 10 | 
				
			||||||
 | 
					  ORB_POINT = 11 | 
				
			||||||
 | 
					  ECEF_POS = 12 | 
				
			||||||
 | 
					  CAMERA_ODO_TRANSLATION = 13 | 
				
			||||||
 | 
					  CAMERA_ODO_ROTATION = 14 | 
				
			||||||
 | 
					  ORB_FEATURES = 15 | 
				
			||||||
 | 
					  MSCKF_TEST = 16 | 
				
			||||||
 | 
					  FEATURE_TRACK_TEST = 17 | 
				
			||||||
 | 
					  LANE_PT = 18 | 
				
			||||||
 | 
					  IMU_FRAME = 19 | 
				
			||||||
 | 
					  PSEUDORANGE_GLONASS = 20 | 
				
			||||||
 | 
					  PSEUDORANGE_RATE_GLONASS = 21 | 
				
			||||||
 | 
					  PSEUDORANGE = 22 | 
				
			||||||
 | 
					  PSEUDORANGE_RATE = 23 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  names = ['Unknown', | 
				
			||||||
 | 
					           'No observation', | 
				
			||||||
 | 
					           'GPS NED', | 
				
			||||||
 | 
					           'Odometric speed', | 
				
			||||||
 | 
					           'Phone gyro', | 
				
			||||||
 | 
					           'GPS velocity', | 
				
			||||||
 | 
					           'GPS pseudorange', | 
				
			||||||
 | 
					           'GPS pseudorange rate', | 
				
			||||||
 | 
					           'Speed', | 
				
			||||||
 | 
					           'No rotation', | 
				
			||||||
 | 
					           'Phone acceleration', | 
				
			||||||
 | 
					           'ORB point', | 
				
			||||||
 | 
					           'ECEF pos', | 
				
			||||||
 | 
					           'camera odometric translation', | 
				
			||||||
 | 
					           'camera odometric rotation', | 
				
			||||||
 | 
					           'ORB features', | 
				
			||||||
 | 
					           'MSCKF test', | 
				
			||||||
 | 
					           'Feature track test', | 
				
			||||||
 | 
					           'Lane ecef point', | 
				
			||||||
 | 
					           'imu frame eulers', | 
				
			||||||
 | 
					           'GLONASS pseudorange', | 
				
			||||||
 | 
					           'GLONASS pseudorange rate'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @classmethod | 
				
			||||||
 | 
					  def to_string(cls, kind): | 
				
			||||||
 | 
					    return cls.names[kind] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SAT_OBS = [ObservationKind.PSEUDORANGE_GPS, | 
				
			||||||
 | 
					           ObservationKind.PSEUDORANGE_RATE_GPS, | 
				
			||||||
 | 
					           ObservationKind.PSEUDORANGE_GLONASS, | 
				
			||||||
 | 
					           ObservationKind.PSEUDORANGE_RATE_GLONASS] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_car_ekf_offline(kf, observations_by_kind): | 
				
			||||||
 | 
					  from laika.raw_gnss import GNSSMeasurement | 
				
			||||||
 | 
					  observations = [] | 
				
			||||||
 | 
					  # create list of observations with element format: [kind, time, data] | 
				
			||||||
 | 
					  for kind in observations_by_kind: | 
				
			||||||
 | 
					    for t, data in zip(observations_by_kind[kind][0], observations_by_kind[kind][1]): | 
				
			||||||
 | 
					      observations.append([t, kind, data]) | 
				
			||||||
 | 
					  observations.sort(key=lambda obs: obs[0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  times, estimates = run_observations_through_filter(kf, observations) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  forward_states = np.stack(e[1] for e in estimates) | 
				
			||||||
 | 
					  forward_covs = np.stack(e[3] for e in estimates) | 
				
			||||||
 | 
					  smoothed_states, smoothed_covs = kf.rts_smooth(estimates) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  observations_dict = {} | 
				
			||||||
 | 
					  # TODO assuming observations and estimates | 
				
			||||||
 | 
					  # are same length may not work with VO | 
				
			||||||
 | 
					  for e in estimates: | 
				
			||||||
 | 
					    t = e[4] | 
				
			||||||
 | 
					    kind = str(int(e[5])) | 
				
			||||||
 | 
					    res = e[6] | 
				
			||||||
 | 
					    z = e[7] | 
				
			||||||
 | 
					    ea = e[8] | 
				
			||||||
 | 
					    if len(z) == 0: | 
				
			||||||
 | 
					      continue | 
				
			||||||
 | 
					    if kind not in observations_dict: | 
				
			||||||
 | 
					      observations_dict[kind] = {} | 
				
			||||||
 | 
					      observations_dict[kind]['t'] = np.array(len(z)*[t]) | 
				
			||||||
 | 
					      observations_dict[kind]['z'] = np.array(z) | 
				
			||||||
 | 
					      observations_dict[kind]['ea'] = np.array(ea) | 
				
			||||||
 | 
					      observations_dict[kind]['residual'] = np.array(res) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      observations_dict[kind]['t'] = np.append(observations_dict[kind]['t'], np.array(len(z)*[t])) | 
				
			||||||
 | 
					      observations_dict[kind]['z'] = np.vstack((observations_dict[kind]['z'], np.array(z))) | 
				
			||||||
 | 
					      observations_dict[kind]['ea'] = np.vstack((observations_dict[kind]['ea'], np.array(ea))) | 
				
			||||||
 | 
					      observations_dict[kind]['residual'] = np.vstack((observations_dict[kind]['residual'], np.array(res))) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # add svIds to gnss data | 
				
			||||||
 | 
					  for kind in map(str, SAT_OBS): | 
				
			||||||
 | 
					    if int(kind) in observations_by_kind and kind in observations_dict: | 
				
			||||||
 | 
					      observations_dict[kind]['svIds'] = np.array([]) | 
				
			||||||
 | 
					      observations_dict[kind]['CNO'] = np.array([]) | 
				
			||||||
 | 
					      observations_dict[kind]['std'] = np.array([]) | 
				
			||||||
 | 
					      for obs in observations_by_kind[int(kind)][1]: | 
				
			||||||
 | 
					        observations_dict[kind]['svIds'] = np.append(observations_dict[kind]['svIds'], | 
				
			||||||
 | 
					                                                     np.array([obs[:,GNSSMeasurement.PRN]])) | 
				
			||||||
 | 
					        observations_dict[kind]['std'] = np.append(observations_dict[kind]['std'], | 
				
			||||||
 | 
					                                                   np.array([obs[:,GNSSMeasurement.PR_STD]])) | 
				
			||||||
 | 
					  return smoothed_states, smoothed_covs, forward_states, forward_covs, times, observations_dict | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_observations_through_filter(kf, observations, filter_time=None): | 
				
			||||||
 | 
					  estimates = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for obs in tqdm(observations): | 
				
			||||||
 | 
					    t = obs[0] | 
				
			||||||
 | 
					    kind = obs[1] | 
				
			||||||
 | 
					    data = obs[2] | 
				
			||||||
 | 
					    estimates.append(kf.predict_and_observe(t, kind, data)) | 
				
			||||||
 | 
					  times = [x[4] for x in estimates] | 
				
			||||||
 | 
					  return times, estimates | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def save_residuals_plot(obs, save_path, data_name): | 
				
			||||||
 | 
					  import matplotlib.pyplot as plt | 
				
			||||||
 | 
					  import mpld3 | 
				
			||||||
 | 
					  fig = plt.figure(figsize=(10,20)) | 
				
			||||||
 | 
					  fig.suptitle('Residuals of ' + data_name, fontsize=24) | 
				
			||||||
 | 
					  n = len(list(obs.keys())) | 
				
			||||||
 | 
					  start_times = [obs[kind]['t'][0] for kind in obs] | 
				
			||||||
 | 
					  start_time = min(start_times) | 
				
			||||||
 | 
					  xlims = [start_time + 3, start_time + 60] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for i, kind in enumerate(obs): | 
				
			||||||
 | 
					    ax = fig.add_subplot(n, 1, i+1) | 
				
			||||||
 | 
					    ax.set_xlim(xlims) | 
				
			||||||
 | 
					    t = obs[kind]['t'] | 
				
			||||||
 | 
					    res = obs[kind]['residual'] | 
				
			||||||
 | 
					    start_idx = bisect(t, xlims[0]) | 
				
			||||||
 | 
					    if len(res) == start_idx: | 
				
			||||||
 | 
					      continue | 
				
			||||||
 | 
					    ylim = max(np.linalg.norm(res[start_idx:], axis=1)) | 
				
			||||||
 | 
					    ax.set_ylim([-ylim, ylim]) | 
				
			||||||
 | 
					    if int(kind) in SAT_OBS: | 
				
			||||||
 | 
					      svIds = obs[kind]['svIds'] | 
				
			||||||
 | 
					      for svId in set(svIds): | 
				
			||||||
 | 
					        svId_idx = (svIds == svId) | 
				
			||||||
 | 
					        t = obs[kind]['t'][svId_idx] | 
				
			||||||
 | 
					        res = obs[kind]['residual'][svId_idx] | 
				
			||||||
 | 
					        ax.plot(t, res, label='SV ' + str(int(svId))) | 
				
			||||||
 | 
					        ax.legend(loc='right') | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      ax.plot(t, res) | 
				
			||||||
 | 
					    plt.title('Residual of kind ' + ObservationKind.to_string(int(kind)), fontsize=20) | 
				
			||||||
 | 
					  plt.tight_layout() | 
				
			||||||
 | 
					  os.makedirs(save_path) | 
				
			||||||
 | 
					  mpld3.save_html(fig, save_path + 'residuals_plot.html') | 
				
			||||||
@ -0,0 +1,323 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from . import loc_model | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from .ekf_sym import EKF_sym | 
				
			||||||
 | 
					from .feature_handler import LstSqComputer, unroll_shutter | 
				
			||||||
 | 
					from laika.raw_gnss import GNSSMeasurement | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_prr(m): | 
				
			||||||
 | 
					  sat_pos_vel_i = np.concatenate((m[GNSSMeasurement.SAT_POS], | 
				
			||||||
 | 
					                                  m[GNSSMeasurement.SAT_VEL])) | 
				
			||||||
 | 
					  R_i = np.atleast_2d(m[GNSSMeasurement.PRR_STD]**2) | 
				
			||||||
 | 
					  z_i = m[GNSSMeasurement.PRR] | 
				
			||||||
 | 
					  return z_i, R_i, sat_pos_vel_i | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_pr(m): | 
				
			||||||
 | 
					  pseudorange = m[GNSSMeasurement.PR] | 
				
			||||||
 | 
					  pseudorange_stdev = m[GNSSMeasurement.PR_STD] | 
				
			||||||
 | 
					  sat_pos_freq_i = np.concatenate((m[GNSSMeasurement.SAT_POS], | 
				
			||||||
 | 
					                                   np.array([m[GNSSMeasurement.GLONASS_FREQ]]))) | 
				
			||||||
 | 
					  z_i = np.atleast_1d(pseudorange) | 
				
			||||||
 | 
					  R_i = np.atleast_2d(pseudorange_stdev**2) | 
				
			||||||
 | 
					  return z_i, R_i, sat_pos_freq_i | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class States(): | 
				
			||||||
 | 
					  ECEF_POS = slice(0,3) # x, y and z in ECEF in meters | 
				
			||||||
 | 
					  ECEF_ORIENTATION = slice(3,7) # quat for pose of phone in ecef | 
				
			||||||
 | 
					  ECEF_VELOCITY = slice(7,10) # ecef velocity in m/s | 
				
			||||||
 | 
					  ANGULAR_VELOCITY = slice(10, 13) # roll, pitch and yaw rates in device frame in radians/s | 
				
			||||||
 | 
					  CLOCK_BIAS = slice(13, 14) # clock bias in light-meters, | 
				
			||||||
 | 
					  CLOCK_DRIFT = slice(14, 15) # clock drift in light-meters/s, | 
				
			||||||
 | 
					  GYRO_BIAS = slice(15, 18) # roll, pitch and yaw biases | 
				
			||||||
 | 
					  ODO_SCALE = slice(18, 19) # odometer scale | 
				
			||||||
 | 
					  ACCELERATION = slice(19, 22) # Acceleration in device frame in m/s**2 | 
				
			||||||
 | 
					  FOCAL_SCALE = slice(22, 23) # focal length scale | 
				
			||||||
 | 
					  IMU_OFFSET = slice(23,26) # imu offset angles in radians | 
				
			||||||
 | 
					  GLONASS_BIAS = slice(26,27) # GLONASS bias in m expressed as bias + freq_num*freq_slope | 
				
			||||||
 | 
					  GLONASS_FREQ_SLOPE = slice(27, 28) # GLONASS bias in m expressed as bias + freq_num*freq_slope | 
				
			||||||
 | 
					  CLOCK_ACCELERATION = slice(28, 29) # clock acceleration in light-meters/s**2, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LocKalman(): | 
				
			||||||
 | 
					  def __init__(self, N=0, max_tracks=3000): | 
				
			||||||
 | 
					    x_initial = np.array([-2.7e6, 4.2e6, 3.8e6, | 
				
			||||||
 | 
					                          1, 0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          1, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          1, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, | 
				
			||||||
 | 
					                          0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # state covariance | 
				
			||||||
 | 
					    P_initial = np.diag([10000**2, 10000**2, 10000**2, | 
				
			||||||
 | 
					                         10**2, 10**2, 10**2, | 
				
			||||||
 | 
					                         10**2, 10**2, 10**2, | 
				
			||||||
 | 
					                         1**2, 1**2, 1**2, | 
				
			||||||
 | 
					                         (200000)**2, (100)**2, | 
				
			||||||
 | 
					                         0.05**2, 0.05**2, 0.05**2, | 
				
			||||||
 | 
					                         0.02**2, | 
				
			||||||
 | 
					                         1**2, 1**2, 1**2, | 
				
			||||||
 | 
					                         0.01**2, | 
				
			||||||
 | 
					                         (0.01)**2, (0.01)**2, (0.01)**2, | 
				
			||||||
 | 
					                         10**2, 1**2, | 
				
			||||||
 | 
					                         0.05**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # process noise | 
				
			||||||
 | 
					    Q = np.diag([0.03**2, 0.03**2, 0.03**2, | 
				
			||||||
 | 
					                 0.0**2, 0.0**2, 0.0**2, | 
				
			||||||
 | 
					                 0.0**2, 0.0**2, 0.0**2, | 
				
			||||||
 | 
					                 0.1**2, 0.1**2, 0.1**2, | 
				
			||||||
 | 
					                 (.1)**2, (0.0)**2, | 
				
			||||||
 | 
					                 (0.005/100)**2, (0.005/100)**2, (0.005/100)**2, | 
				
			||||||
 | 
					                 (0.02/100)**2, | 
				
			||||||
 | 
					                 3**2, 3**2, 3**2, | 
				
			||||||
 | 
					                 0.001**2, | 
				
			||||||
 | 
					                 (0.05/60)**2, (0.05/60)**2, (0.05/60)**2, | 
				
			||||||
 | 
					                 (.1)**2, (.01)**2, | 
				
			||||||
 | 
					                 0.005**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.obs_noise = {ObservationKind.ODOMETRIC_SPEED: np.atleast_2d(0.2**2), | 
				
			||||||
 | 
					                      ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2]), | 
				
			||||||
 | 
					                      ObservationKind.PHONE_ACCEL:  np.diag([.5**2, .5**2, .5*2]), | 
				
			||||||
 | 
					                      ObservationKind.CAMERA_ODO_ROTATION:  np.diag([0.05**2, 0.05**2, 0.05**2]), | 
				
			||||||
 | 
					                      ObservationKind.IMU_FRAME:  np.diag([0.05**2, 0.05**2, 0.05**2]), | 
				
			||||||
 | 
					                      ObservationKind.NO_ROT: np.diag([0.00025**2, 0.00025**2, 0.00025**2]), | 
				
			||||||
 | 
					                      ObservationKind.ECEF_POS: np.diag([5**2, 5**2, 5**2])} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # MSCKF stuff | 
				
			||||||
 | 
					    self.N = N | 
				
			||||||
 | 
					    self.dim_main = x_initial.shape[0] | 
				
			||||||
 | 
					    self.dim_augment = 7 | 
				
			||||||
 | 
					    self.dim_main_err = P_initial.shape[0] | 
				
			||||||
 | 
					    self.dim_augment_err = 6 | 
				
			||||||
 | 
					    self.dim_state = self.dim_main + self.dim_augment*self.N | 
				
			||||||
 | 
					    self.dim_state_err = self.dim_main_err + self.dim_augment_err*self.N | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # mahalanobis outlier rejection | 
				
			||||||
 | 
					    maha_test_kinds = [ObservationKind.ORB_FEATURES] #, ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_RATE] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'loc_%d' % N | 
				
			||||||
 | 
					    loc_model.gen_model(name, N, self.dim_main, self.dim_main_err, | 
				
			||||||
 | 
					                        self.dim_augment, self.dim_augment_err, | 
				
			||||||
 | 
					                        self.dim_state, self.dim_state_err, | 
				
			||||||
 | 
					                        maha_test_kinds) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if self.N > 0: | 
				
			||||||
 | 
					      x_initial, P_initial, Q = self.pad_augmented(x_initial, P_initial, Q) | 
				
			||||||
 | 
					      self.computer = LstSqComputer(N) | 
				
			||||||
 | 
					      self.max_tracks = max_tracks | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # init filter | 
				
			||||||
 | 
					    self.filter = EKF_sym(name, Q, x_initial, P_initial, self.dim_main, self.dim_main_err, | 
				
			||||||
 | 
					                          N, self.dim_augment, self.dim_augment_err, maha_test_kinds) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def x(self): | 
				
			||||||
 | 
					    return self.filter.state() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def t(self): | 
				
			||||||
 | 
					    return self.filter.filter_time | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def P(self): | 
				
			||||||
 | 
					    return self.filter.covs() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict(self, t): | 
				
			||||||
 | 
					    return self.filter.predict(t) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rts_smooth(self, estimates): | 
				
			||||||
 | 
					    return self.filter.rts_smooth(estimates, norm_quats=True) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pad_augmented(self, x, P, Q=None): | 
				
			||||||
 | 
					    if x.shape[0] == self.dim_main and self.N > 0: | 
				
			||||||
 | 
					      x = np.pad(x, (0, self.N*self.dim_augment), mode='constant') | 
				
			||||||
 | 
					      x[self.dim_main+3::7] = 1 | 
				
			||||||
 | 
					    if P.shape[0] == self.dim_main_err and self.N > 0: | 
				
			||||||
 | 
					      P = np.pad(P, [(0, self.N*self.dim_augment_err), (0, self.N*self.dim_augment_err)], mode='constant') | 
				
			||||||
 | 
					      P[self.dim_main_err:, self.dim_main_err:] = 10e20*np.eye(self.dim_augment_err *self.N) | 
				
			||||||
 | 
					    if Q is None: | 
				
			||||||
 | 
					      return x, P | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      Q = np.pad(Q, [(0, self.N*self.dim_augment_err), (0, self.N*self.dim_augment_err)], mode='constant') | 
				
			||||||
 | 
					      return x, P, Q | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def init_state(self, state, covs_diag=None, covs=None, filter_time=None): | 
				
			||||||
 | 
					    if covs_diag is not None: | 
				
			||||||
 | 
					      P = np.diag(covs_diag) | 
				
			||||||
 | 
					    elif covs is not None: | 
				
			||||||
 | 
					      P = covs | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      P = self.filter.covs() | 
				
			||||||
 | 
					    state, P = self.pad_augmented(state, P) | 
				
			||||||
 | 
					    self.filter.init_state(state, P, filter_time) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_observe(self, t, kind, data): | 
				
			||||||
 | 
					    if len(data) > 0: | 
				
			||||||
 | 
					      data = np.atleast_2d(data) | 
				
			||||||
 | 
					    if kind == ObservationKind.CAMERA_ODO_TRANSLATION: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_trans(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.CAMERA_ODO_ROTATION: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_rot(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.PSEUDORANGE_GPS or kind == ObservationKind.PSEUDORANGE_GLONASS: | 
				
			||||||
 | 
					      r = self.predict_and_update_pseudorange(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.PSEUDORANGE_RATE_GPS or kind == ObservationKind.PSEUDORANGE_RATE_GLONASS: | 
				
			||||||
 | 
					      r = self.predict_and_update_pseudorange_rate(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.ORB_POINT: | 
				
			||||||
 | 
					      r = self.predict_and_update_orb(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.ORB_FEATURES: | 
				
			||||||
 | 
					      r = self.predict_and_update_orb_features(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.MSCKF_TEST: | 
				
			||||||
 | 
					      r = self.predict_and_update_msckf_test(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.FEATURE_TRACK_TEST: | 
				
			||||||
 | 
					      r = self.predict_and_update_feature_track_test(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.ODOMETRIC_SPEED: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_speed(data, t, kind) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      r = self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) | 
				
			||||||
 | 
					    # Normalize quats | 
				
			||||||
 | 
					    quat_norm = np.linalg.norm(self.filter.x[3:7,0]) | 
				
			||||||
 | 
					    # Should not continue if the quats behave this weirdly | 
				
			||||||
 | 
					    if not 0.1 < quat_norm < 10: | 
				
			||||||
 | 
					      raise RuntimeError("Sir! The filter's gone all wobbly!") | 
				
			||||||
 | 
					    self.filter.x[3:7,0] = self.filter.x[3:7,0]/quat_norm | 
				
			||||||
 | 
					    for i in range(self.N): | 
				
			||||||
 | 
					      d1 = self.dim_main | 
				
			||||||
 | 
					      d3 = self.dim_augment | 
				
			||||||
 | 
					      self.filter.x[d1+d3*i+3:d1+d3*i+7] /= np.linalg.norm(self.filter.x[d1+i*d3 + 3:d1+i*d3 + 7,0]) | 
				
			||||||
 | 
					    return r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def get_R(self, kind, n): | 
				
			||||||
 | 
					    obs_noise = self.obs_noise[kind] | 
				
			||||||
 | 
					    dim = obs_noise.shape[0] | 
				
			||||||
 | 
					    R = np.zeros((n, dim, dim)) | 
				
			||||||
 | 
					    for i in range(n): | 
				
			||||||
 | 
					      R[i,:,:] = obs_noise | 
				
			||||||
 | 
					    return R | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_pseudorange(self, meas, t, kind): | 
				
			||||||
 | 
					    R = np.zeros((len(meas), 1, 1)) | 
				
			||||||
 | 
					    sat_pos_freq = np.zeros((len(meas), 4)) | 
				
			||||||
 | 
					    z = np.zeros((len(meas), 1)) | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z_i, R_i, sat_pos_freq_i = parse_pr(m) | 
				
			||||||
 | 
					      sat_pos_freq[i,:] = sat_pos_freq_i | 
				
			||||||
 | 
					      z[i,:] = z_i | 
				
			||||||
 | 
					      R[i,:,:] = R_i | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_freq) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_pseudorange_rate(self, meas, t, kind): | 
				
			||||||
 | 
					    R = np.zeros((len(meas), 1, 1)) | 
				
			||||||
 | 
					    z = np.zeros((len(meas), 1)) | 
				
			||||||
 | 
					    sat_pos_vel = np.zeros((len(meas), 6)) | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z_i, R_i, sat_pos_vel_i = parse_prr(m) | 
				
			||||||
 | 
					      sat_pos_vel[i] = sat_pos_vel_i | 
				
			||||||
 | 
					      R[i,:,:] = R_i | 
				
			||||||
 | 
					      z[i, :] = z_i | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_vel) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_orb(self, orb, t, kind): | 
				
			||||||
 | 
					    true_pos = orb[:,2:] | 
				
			||||||
 | 
					    z = orb[:,:2] | 
				
			||||||
 | 
					    R = np.zeros((len(orb), 2, 2)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					      R[i,:,:] = np.diag([10**2, 10**2]) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R, true_pos) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_speed(self, speed, t, kind): | 
				
			||||||
 | 
					    z = np.array(speed) | 
				
			||||||
 | 
					    R = np.zeros((len(speed), 1, 1)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					      R[i,:,:] = np.diag([0.2**2]) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_trans(self, trans, t, kind): | 
				
			||||||
 | 
					    z = trans[:,:3] | 
				
			||||||
 | 
					    R = np.zeros((len(trans), 3, 3)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					        R[i,:,:] = np.diag(trans[i,3:]**2) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_rot(self, rot, t, kind): | 
				
			||||||
 | 
					    z = rot[:,:3] | 
				
			||||||
 | 
					    R = np.zeros((len(rot), 3, 3)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					        R[i,:,:] = np.diag(rot[i,3:]**2) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_orb_features(self, tracks, t, kind): | 
				
			||||||
 | 
					    k = 2*(self.N+1) | 
				
			||||||
 | 
					    R = np.zeros((len(tracks), k, k)) | 
				
			||||||
 | 
					    z = np.zeros((len(tracks), k)) | 
				
			||||||
 | 
					    ecef_pos = np.zeros((len(tracks), 3)) | 
				
			||||||
 | 
					    ecef_pos[:] = np.nan | 
				
			||||||
 | 
					    poses = self.x[self.dim_main:].reshape((-1,7)) | 
				
			||||||
 | 
					    times = tracks.reshape((len(tracks),self.N+1, 4))[:,:,0] | 
				
			||||||
 | 
					    good_counter = 0 | 
				
			||||||
 | 
					    if times.any() and np.allclose(times[0,:-1], self.filter.augment_times, rtol=1e-6): | 
				
			||||||
 | 
					      for i, track in enumerate(tracks): | 
				
			||||||
 | 
					        img_positions = track.reshape((self.N+1, 4))[:,2:] | 
				
			||||||
 | 
					        # TODO not perfect as last pose not used | 
				
			||||||
 | 
					        #img_positions = unroll_shutter(img_positions, poses, self.filter.state()[7:10], self.filter.state()[10:13], ecef_pos[i]) | 
				
			||||||
 | 
					        ecef_pos[i] = self.computer.compute_pos(poses, img_positions[:-1]) | 
				
			||||||
 | 
					        z[i] = img_positions.flatten() | 
				
			||||||
 | 
					        R[i,:,:] = np.diag([0.005**2]*(k)) | 
				
			||||||
 | 
					        if np.isfinite(ecef_pos[i][0]): | 
				
			||||||
 | 
					          good_counter += 1 | 
				
			||||||
 | 
					          if good_counter > self.max_tracks: | 
				
			||||||
 | 
					            break | 
				
			||||||
 | 
					    good_idxs = np.all(np.isfinite(ecef_pos),axis=1) | 
				
			||||||
 | 
					    # have to do some weird stuff here to keep | 
				
			||||||
 | 
					    # to have the observations input from mesh3d | 
				
			||||||
 | 
					    # consistent with the outputs of the filter | 
				
			||||||
 | 
					    # Probably should be replaced, not sure how. | 
				
			||||||
 | 
					    ret = self.filter.predict_and_update_batch(t, kind, z[good_idxs], R[good_idxs], ecef_pos[good_idxs], augment=True) | 
				
			||||||
 | 
					    if ret is None: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    y_full = np.zeros((z.shape[0], z.shape[1] - 3)) | 
				
			||||||
 | 
					    #print sum(good_idxs), len(tracks) | 
				
			||||||
 | 
					    if sum(good_idxs) > 0: | 
				
			||||||
 | 
					        y_full[good_idxs] = np.array(ret[6]) | 
				
			||||||
 | 
					    ret = ret[:6] + (y_full, z, ecef_pos) | 
				
			||||||
 | 
					    return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_msckf_test(self, test_data, t, kind): | 
				
			||||||
 | 
					    assert self.N > 0 | 
				
			||||||
 | 
					    z = test_data | 
				
			||||||
 | 
					    R = np.zeros((len(test_data), len(z[0]), len(z[0]))) | 
				
			||||||
 | 
					    ecef_pos = [self.x[:3]] | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					      R[i,:,:] = np.diag([0.1**2]*len(z[0])) | 
				
			||||||
 | 
					    ret = self.filter.predict_and_update_batch(t, kind, z, R, ecef_pos) | 
				
			||||||
 | 
					    self.filter.augment() | 
				
			||||||
 | 
					    return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def maha_test_pseudorange(self, x, P, meas, kind, maha_thresh=.3): | 
				
			||||||
 | 
					    bools = [] | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z, R, sat_pos_freq = parse_pr(m) | 
				
			||||||
 | 
					      bools.append(self.filter.maha_test(x, P, kind, z, R, extra_args=sat_pos_freq, maha_thresh=maha_thresh)) | 
				
			||||||
 | 
					    return np.array(bools) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def maha_test_pseudorange_rate(self, x, P, meas, kind, maha_thresh=.999): | 
				
			||||||
 | 
					    bools = [] | 
				
			||||||
 | 
					    for i, m in enumerate(meas): | 
				
			||||||
 | 
					      z, R, sat_pos_vel = parse_prr(m) | 
				
			||||||
 | 
					      bools.append(self.filter.maha_test(x, P, kind, z, R, extra_args=sat_pos_vel, maha_thresh=maha_thresh)) | 
				
			||||||
 | 
					    return np.array(bools) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  LocKalman(N=4) | 
				
			||||||
@ -0,0 +1,128 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from selfdrive.locationd.kalman import loc_local_model | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.ekf_sym import EKF_sym | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class States(): | 
				
			||||||
 | 
					  VELOCITY = slice(0,3) # device frame velocity in m/s | 
				
			||||||
 | 
					  ANGULAR_VELOCITY = slice(3, 6) # roll, pitch and yaw rates in device frame in radians/s | 
				
			||||||
 | 
					  GYRO_BIAS = slice(6, 9) # roll, pitch and yaw biases | 
				
			||||||
 | 
					  ODO_SCALE = slice(9, 10) # odometer scale | 
				
			||||||
 | 
					  ACCELERATION = slice(10, 13) # Acceleration in device frame in m/s**2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LocLocalKalman(): | 
				
			||||||
 | 
					  def __init__(self): | 
				
			||||||
 | 
					    x_initial = np.array([0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          0, 0, 0, | 
				
			||||||
 | 
					                          1, | 
				
			||||||
 | 
					                          0, 0, 0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # state covariance | 
				
			||||||
 | 
					    P_initial = np.diag([10**2, 10**2, 10**2, | 
				
			||||||
 | 
					                         1**2, 1**2, 1**2, | 
				
			||||||
 | 
					                         0.05**2, 0.05**2, 0.05**2, | 
				
			||||||
 | 
					                         0.02**2, | 
				
			||||||
 | 
					                         1**2, 1**2, 1**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # process noise | 
				
			||||||
 | 
					    Q = np.diag([0.0**2, 0.0**2, 0.0**2, | 
				
			||||||
 | 
					                 .01**2, .01**2, .01**2, | 
				
			||||||
 | 
					                 (0.005/100)**2, (0.005/100)**2, (0.005/100)**2, | 
				
			||||||
 | 
					                 (0.02/100)**2, | 
				
			||||||
 | 
					                 3**2, 3**2, 3**2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.obs_noise = {ObservationKind.ODOMETRIC_SPEED: np.atleast_2d(0.2**2), | 
				
			||||||
 | 
					                      ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2])} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # MSCKF stuff | 
				
			||||||
 | 
					    self.dim_state = len(x_initial) | 
				
			||||||
 | 
					    self.dim_main = self.dim_state | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'loc_local' | 
				
			||||||
 | 
					    loc_local_model.gen_model(name, self.dim_state) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # init filter | 
				
			||||||
 | 
					    self.filter = EKF_sym(name, Q, x_initial, P_initial, self.dim_main, self.dim_main) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def x(self): | 
				
			||||||
 | 
					    return self.filter.state() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def t(self): | 
				
			||||||
 | 
					    return self.filter.filter_time | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @property | 
				
			||||||
 | 
					  def P(self): | 
				
			||||||
 | 
					    return self.filter.covs() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict(self, t): | 
				
			||||||
 | 
					    if self.t: | 
				
			||||||
 | 
					      # Does NOT modify filter state | 
				
			||||||
 | 
					      return self.filter._predict(self.x, self.P, t - self.t)[0] | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      raise RuntimeError("Request predict on filter with uninitialized time") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def rts_smooth(self, estimates): | 
				
			||||||
 | 
					    return self.filter.rts_smooth(estimates, norm_quats=True) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def init_state(self, state, covs_diag=None, covs=None, filter_time=None): | 
				
			||||||
 | 
					    if covs_diag is not None: | 
				
			||||||
 | 
					      P = np.diag(covs_diag) | 
				
			||||||
 | 
					    elif covs is not None: | 
				
			||||||
 | 
					      P = covs | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      P = self.filter.covs() | 
				
			||||||
 | 
					    self.filter.init_state(state, P, filter_time) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_observe(self, t, kind, data): | 
				
			||||||
 | 
					    if len(data) > 0: | 
				
			||||||
 | 
					      data = np.atleast_2d(data) | 
				
			||||||
 | 
					    if kind == ObservationKind.CAMERA_ODO_TRANSLATION: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_trans(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.CAMERA_ODO_ROTATION: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_rot(data, t, kind) | 
				
			||||||
 | 
					    elif kind == ObservationKind.ODOMETRIC_SPEED: | 
				
			||||||
 | 
					      r = self.predict_and_update_odo_speed(data, t, kind) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      r = self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) | 
				
			||||||
 | 
					    return r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def get_R(self, kind, n): | 
				
			||||||
 | 
					    obs_noise = self.obs_noise[kind] | 
				
			||||||
 | 
					    dim = obs_noise.shape[0] | 
				
			||||||
 | 
					    R = np.zeros((n, dim, dim)) | 
				
			||||||
 | 
					    for i in range(n): | 
				
			||||||
 | 
					      R[i,:,:] = obs_noise | 
				
			||||||
 | 
					    return R | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_speed(self, speed, t, kind): | 
				
			||||||
 | 
					    z = np.array(speed) | 
				
			||||||
 | 
					    R = np.zeros((len(speed), 1, 1)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					      R[i,:,:] = np.diag([0.2**2]) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_trans(self, trans, t, kind): | 
				
			||||||
 | 
					    z = trans[:,:3] | 
				
			||||||
 | 
					    R = np.zeros((len(trans), 3, 3)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					        R[i,:,:] = np.diag(trans[i,3:]**2) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def predict_and_update_odo_rot(self, rot, t, kind): | 
				
			||||||
 | 
					    z = rot[:,:3] | 
				
			||||||
 | 
					    R = np.zeros((len(rot), 3, 3)) | 
				
			||||||
 | 
					    for i, _ in enumerate(z): | 
				
			||||||
 | 
					        R[i,:,:] = np.diag(rot[i,3:]**2) | 
				
			||||||
 | 
					    return self.filter.predict_and_update_batch(t, kind, z, R) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  LocLocalKalman() | 
				
			||||||
@ -0,0 +1,80 @@ | 
				
			|||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import sympy as sp | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.ekf_sym import gen_code | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_model(name, dim_state): | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # check if rebuild is needed | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    dir_path = os.path.dirname(__file__) | 
				
			||||||
 | 
					    deps = [dir_path + '/' + 'ekf_c.c', | 
				
			||||||
 | 
					            dir_path + '/' + 'ekf_sym.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'loc_local_model.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'loc_local_kf.py'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outs = [dir_path + '/' + name + '.o', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.so', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.cpp'] | 
				
			||||||
 | 
					    out_times = list(map(os.path.getmtime, outs)) | 
				
			||||||
 | 
					    dep_times = list(map(os.path.getmtime, deps)) | 
				
			||||||
 | 
					    rebuild = os.getenv("REBUILD", False) | 
				
			||||||
 | 
					    if min(out_times) > max(dep_times) and not rebuild: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    list(map(os.remove, outs)) | 
				
			||||||
 | 
					  except OSError: | 
				
			||||||
 | 
					    pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # make functions and jacobians with sympy | 
				
			||||||
 | 
					  # state variables | 
				
			||||||
 | 
					  state_sym = sp.MatrixSymbol('state', dim_state, 1) | 
				
			||||||
 | 
					  state = sp.Matrix(state_sym) | 
				
			||||||
 | 
					  v = state[0:3,:] | 
				
			||||||
 | 
					  omega = state[3:6,:] | 
				
			||||||
 | 
					  vroll, vpitch, vyaw = omega | 
				
			||||||
 | 
					  vx, vy, vz = v | 
				
			||||||
 | 
					  roll_bias, pitch_bias, yaw_bias = state[6:9,:] | 
				
			||||||
 | 
					  odo_scale = state[9,:] | 
				
			||||||
 | 
					  accel = state[10:13,:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dt = sp.Symbol('dt') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Time derivative of the state as a function of state | 
				
			||||||
 | 
					  state_dot = sp.Matrix(np.zeros((dim_state, 1))) | 
				
			||||||
 | 
					  state_dot[:3,:] = accel | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Basic descretization, 1st order intergrator | 
				
			||||||
 | 
					  # Can be pretty bad if dt is big | 
				
			||||||
 | 
					  f_sym = sp.Matrix(state + dt*state_dot) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					  # Observation functions | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # extra args | 
				
			||||||
 | 
					  #imu_rot = euler_rotate(*imu_angles) | 
				
			||||||
 | 
					  #h_gyro_sym = imu_rot*sp.Matrix([vroll + roll_bias, | 
				
			||||||
 | 
					  #                               vpitch + pitch_bias, | 
				
			||||||
 | 
					  #                               vyaw + yaw_bias]) | 
				
			||||||
 | 
					  h_gyro_sym = sp.Matrix([vroll + roll_bias, | 
				
			||||||
 | 
					                          vpitch + pitch_bias, | 
				
			||||||
 | 
					                          vyaw + yaw_bias]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  speed = vx**2 + vy**2 + vz**2 | 
				
			||||||
 | 
					  h_speed_sym = sp.Matrix([sp.sqrt(speed)*odo_scale]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_relative_motion = sp.Matrix(v) | 
				
			||||||
 | 
					  h_phone_rot_sym = sp.Matrix([vroll, | 
				
			||||||
 | 
					                               vpitch, | 
				
			||||||
 | 
					                               vyaw]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  obs_eqs = [[h_speed_sym, ObservationKind.ODOMETRIC_SPEED, None], | 
				
			||||||
 | 
					             [h_gyro_sym, ObservationKind.PHONE_GYRO, None], | 
				
			||||||
 | 
					             [h_phone_rot_sym, ObservationKind.NO_ROT, None], | 
				
			||||||
 | 
					             [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], | 
				
			||||||
 | 
					             [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None]] | 
				
			||||||
 | 
					  gen_code(name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state) | 
				
			||||||
@ -0,0 +1,254 @@ | 
				
			|||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import sympy as sp | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from laika.constants import EARTH_GM | 
				
			||||||
 | 
					from .kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from .ekf_sym import gen_code | 
				
			||||||
 | 
					from common.sympy_helpers import cross, euler_rotate, quat_rotate, quat_matrix_l, quat_matrix_r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_model(name, N, dim_main, dim_main_err, | 
				
			||||||
 | 
					                 dim_augment, dim_augment_err, | 
				
			||||||
 | 
					                 dim_state, dim_state_err, | 
				
			||||||
 | 
					                 maha_test_kinds): | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # check if rebuild is needed | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    dir_path = os.path.dirname(__file__) | 
				
			||||||
 | 
					    deps = [dir_path + '/' + 'ekf_c.c', | 
				
			||||||
 | 
					            dir_path + '/' + 'ekf_sym.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'loc_model.py', | 
				
			||||||
 | 
					            dir_path + '/' + 'loc_kf.py'] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outs = [dir_path + '/' + name + '.o', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.so', | 
				
			||||||
 | 
					            dir_path + '/' + name + '.cpp'] | 
				
			||||||
 | 
					    out_times = list(map(os.path.getmtime, outs)) | 
				
			||||||
 | 
					    dep_times = list(map(os.path.getmtime, deps)) | 
				
			||||||
 | 
					    rebuild = os.getenv("REBUILD", False) | 
				
			||||||
 | 
					    if min(out_times) > max(dep_times) and not rebuild: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    list(map(os.remove, outs)) | 
				
			||||||
 | 
					  except OSError as e: | 
				
			||||||
 | 
					    pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # make functions and jacobians with sympy | 
				
			||||||
 | 
					  # state variables | 
				
			||||||
 | 
					  state_sym = sp.MatrixSymbol('state', dim_state, 1) | 
				
			||||||
 | 
					  state = sp.Matrix(state_sym) | 
				
			||||||
 | 
					  x,y,z = state[0:3,:] | 
				
			||||||
 | 
					  q = state[3:7,:] | 
				
			||||||
 | 
					  v = state[7:10,:] | 
				
			||||||
 | 
					  vx, vy, vz = v | 
				
			||||||
 | 
					  omega = state[10:13,:] | 
				
			||||||
 | 
					  vroll, vpitch, vyaw = omega | 
				
			||||||
 | 
					  cb, cd = state[13:15,:] | 
				
			||||||
 | 
					  roll_bias, pitch_bias, yaw_bias = state[15:18,:] | 
				
			||||||
 | 
					  odo_scale = state[18,:] | 
				
			||||||
 | 
					  acceleration = state[19:22,:] | 
				
			||||||
 | 
					  focal_scale = state[22,:] | 
				
			||||||
 | 
					  imu_angles= state[23:26,:] | 
				
			||||||
 | 
					  glonass_bias, glonass_freq_slope = state[26:28,:] | 
				
			||||||
 | 
					  ca = state[28,0] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dt = sp.Symbol('dt') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # calibration and attitude rotation matrices | 
				
			||||||
 | 
					  quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Got the quat predict equations from here | 
				
			||||||
 | 
					  # A New Quaternion-Based Kalman Filter for | 
				
			||||||
 | 
					  # Real-Time Attitude Estimation Using the Two-Step | 
				
			||||||
 | 
					  # Geometrically-Intuitive Correction Algorithm | 
				
			||||||
 | 
					  A = 0.5*sp.Matrix([[0, -vroll, -vpitch, -vyaw], | 
				
			||||||
 | 
					                 [vroll, 0, vyaw, -vpitch], | 
				
			||||||
 | 
					                 [vpitch, -vyaw, 0, vroll], | 
				
			||||||
 | 
					                 [vyaw, vpitch, -vroll, 0]]) | 
				
			||||||
 | 
					  q_dot = A * q | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Time derivative of the state as a function of state | 
				
			||||||
 | 
					  state_dot = sp.Matrix(np.zeros((dim_state, 1))) | 
				
			||||||
 | 
					  state_dot[:3,:] = v | 
				
			||||||
 | 
					  state_dot[3:7,:] = q_dot | 
				
			||||||
 | 
					  state_dot[7:10,0] = quat_rot * acceleration | 
				
			||||||
 | 
					  state_dot[13,0] = cd | 
				
			||||||
 | 
					  state_dot[14,0] = ca | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Basic descretization, 1st order intergrator | 
				
			||||||
 | 
					  # Can be pretty bad if dt is big | 
				
			||||||
 | 
					  f_sym = state + dt*state_dot | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state_err_sym = sp.MatrixSymbol('state_err',dim_state_err,1) | 
				
			||||||
 | 
					  state_err = sp.Matrix(state_err_sym) | 
				
			||||||
 | 
					  quat_err = state_err[3:6,:] | 
				
			||||||
 | 
					  v_err = state_err[6:9,:] | 
				
			||||||
 | 
					  omega_err = state_err[9:12,:] | 
				
			||||||
 | 
					  cd_err = state_err[13,:] | 
				
			||||||
 | 
					  acceleration_err = state_err[18:21,:] | 
				
			||||||
 | 
					  ca_err = state_err[27,:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Time derivative of the state error as a function of state error and state | 
				
			||||||
 | 
					  quat_err_matrix = euler_rotate(quat_err[0], quat_err[1], quat_err[2]) | 
				
			||||||
 | 
					  q_err_dot = quat_err_matrix * quat_rot * (omega + omega_err) | 
				
			||||||
 | 
					  state_err_dot = sp.Matrix(np.zeros((dim_state_err, 1))) | 
				
			||||||
 | 
					  state_err_dot[:3,:] = v_err | 
				
			||||||
 | 
					  state_err_dot[3:6,:] = q_err_dot | 
				
			||||||
 | 
					  state_err_dot[6:9,:] = quat_err_matrix * quat_rot * (acceleration + acceleration_err) | 
				
			||||||
 | 
					  state_err_dot[12,:] = cd_err | 
				
			||||||
 | 
					  state_err_dot[13,:] = ca_err | 
				
			||||||
 | 
					  f_err_sym = state_err + dt*state_err_dot | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # convenient indexing | 
				
			||||||
 | 
					  # q idxs are for quats and p idxs are for other | 
				
			||||||
 | 
					  q_idxs = [[3, dim_augment]] + [[dim_main + n*dim_augment + 3, dim_main + (n+1)*dim_augment] for n in range(N)] | 
				
			||||||
 | 
					  q_err_idxs = [[3, dim_augment_err]] + [[dim_main_err + n*dim_augment_err + 3, dim_main_err + (n+1)*dim_augment_err] for n in range(N)] | 
				
			||||||
 | 
					  p_idxs = [[0, 3]] + [[dim_augment, dim_main]] + [[dim_main + n*dim_augment , dim_main + n*dim_augment + 3] for n in range(N)] | 
				
			||||||
 | 
					  p_err_idxs = [[0, 3]] + [[dim_augment_err, dim_main_err]] + [[dim_main_err + n*dim_augment_err, dim_main_err + n*dim_augment_err + 3] for n in range(N)] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Observation matrix modifier | 
				
			||||||
 | 
					  H_mod_sym = sp.Matrix(np.zeros((dim_state, dim_state_err))) | 
				
			||||||
 | 
					  for p_idx, p_err_idx in zip(p_idxs, p_err_idxs): | 
				
			||||||
 | 
					    H_mod_sym[p_idx[0]:p_idx[1],p_err_idx[0]:p_err_idx[1]] = np.eye(p_idx[1]-p_idx[0]) | 
				
			||||||
 | 
					  for q_idx, q_err_idx in zip(q_idxs, q_err_idxs): | 
				
			||||||
 | 
					    H_mod_sym[q_idx[0]:q_idx[1],q_err_idx[0]:q_err_idx[1]] = 0.5*quat_matrix_r(state[q_idx[0]:q_idx[1]])[:,1:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # these error functions are defined so that say there | 
				
			||||||
 | 
					  # is a nominal x and true x: | 
				
			||||||
 | 
					  # true x = err_function(nominal x, delta x) | 
				
			||||||
 | 
					  # delta x = inv_err_function(nominal x, true x) | 
				
			||||||
 | 
					  nom_x = sp.MatrixSymbol('nom_x',dim_state,1) | 
				
			||||||
 | 
					  true_x = sp.MatrixSymbol('true_x',dim_state,1) | 
				
			||||||
 | 
					  delta_x = sp.MatrixSymbol('delta_x',dim_state_err,1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err_function_sym = sp.Matrix(np.zeros((dim_state,1))) | 
				
			||||||
 | 
					  for q_idx, q_err_idx in zip(q_idxs, q_err_idxs): | 
				
			||||||
 | 
					    delta_quat = sp.Matrix(np.ones((4))) | 
				
			||||||
 | 
					    delta_quat[1:,:] = sp.Matrix(0.5*delta_x[q_err_idx[0]: q_err_idx[1],:]) | 
				
			||||||
 | 
					    err_function_sym[q_idx[0]:q_idx[1],0] = quat_matrix_r(nom_x[q_idx[0]:q_idx[1],0])*delta_quat | 
				
			||||||
 | 
					  for p_idx, p_err_idx in zip(p_idxs, p_err_idxs): | 
				
			||||||
 | 
					    err_function_sym[p_idx[0]:p_idx[1],:] = sp.Matrix(nom_x[p_idx[0]:p_idx[1],:] + delta_x[p_err_idx[0]:p_err_idx[1],:]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inv_err_function_sym = sp.Matrix(np.zeros((dim_state_err,1))) | 
				
			||||||
 | 
					  for p_idx, p_err_idx in zip(p_idxs, p_err_idxs): | 
				
			||||||
 | 
					    inv_err_function_sym[p_err_idx[0]:p_err_idx[1],0] = sp.Matrix(-nom_x[p_idx[0]:p_idx[1],0] + true_x[p_idx[0]:p_idx[1],0]) | 
				
			||||||
 | 
					  for q_idx, q_err_idx in zip(q_idxs, q_err_idxs): | 
				
			||||||
 | 
					    delta_quat = quat_matrix_r(nom_x[q_idx[0]:q_idx[1],0]).T*true_x[q_idx[0]:q_idx[1],0] | 
				
			||||||
 | 
					    inv_err_function_sym[q_err_idx[0]:q_err_idx[1],0] = sp.Matrix(2*delta_quat[1:]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  eskf_params = [[err_function_sym, nom_x, delta_x], | 
				
			||||||
 | 
					                 [inv_err_function_sym, nom_x, true_x], | 
				
			||||||
 | 
					                 H_mod_sym, f_err_sym, state_err_sym] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					  # Observation functions | 
				
			||||||
 | 
					  # | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # extra args | 
				
			||||||
 | 
					  sat_pos_freq_sym = sp.MatrixSymbol('sat_pos', 4, 1) | 
				
			||||||
 | 
					  sat_pos_vel_sym = sp.MatrixSymbol('sat_pos_vel', 6, 1) | 
				
			||||||
 | 
					  sat_los_sym = sp.MatrixSymbol('sat_los', 3, 1) | 
				
			||||||
 | 
					  orb_epos_sym = sp.MatrixSymbol('orb_epos_sym', 3, 1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # expand extra args | 
				
			||||||
 | 
					  sat_x, sat_y, sat_z, glonass_freq = sat_pos_freq_sym | 
				
			||||||
 | 
					  sat_vx, sat_vy, sat_vz = sat_pos_vel_sym[3:] | 
				
			||||||
 | 
					  los_x, los_y, los_z = sat_los_sym | 
				
			||||||
 | 
					  orb_x, orb_y, orb_z = orb_epos_sym | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_pseudorange_sym = sp.Matrix([sp.sqrt( | 
				
			||||||
 | 
					                                  (x - sat_x)**2 + | 
				
			||||||
 | 
					                                  (y - sat_y)**2 + | 
				
			||||||
 | 
					                                  (z - sat_z)**2) + | 
				
			||||||
 | 
					                                  cb]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_pseudorange_glonass_sym = sp.Matrix([sp.sqrt( | 
				
			||||||
 | 
					                                  (x - sat_x)**2 + | 
				
			||||||
 | 
					                                  (y - sat_y)**2 + | 
				
			||||||
 | 
					                                  (z - sat_z)**2) + | 
				
			||||||
 | 
					                                  cb + glonass_bias + glonass_freq_slope*glonass_freq]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  los_vector = (sp.Matrix(sat_pos_vel_sym[0:3]) - sp.Matrix([x, y, z])) | 
				
			||||||
 | 
					  los_vector = los_vector / sp.sqrt(los_vector[0]**2 + los_vector[1]**2 + los_vector[2]**2) | 
				
			||||||
 | 
					  h_pseudorange_rate_sym = sp.Matrix([los_vector[0]*(sat_vx - vx) + | 
				
			||||||
 | 
					                                         los_vector[1]*(sat_vy - vy) + | 
				
			||||||
 | 
					                                         los_vector[2]*(sat_vz - vz) + | 
				
			||||||
 | 
					                                         cd]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  imu_rot = euler_rotate(*imu_angles) | 
				
			||||||
 | 
					  h_gyro_sym = imu_rot*sp.Matrix([vroll + roll_bias, | 
				
			||||||
 | 
					                                 vpitch + pitch_bias, | 
				
			||||||
 | 
					                                 vyaw + yaw_bias]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pos = sp.Matrix([x, y, z]) | 
				
			||||||
 | 
					  gravity = quat_rot.T * ((EARTH_GM/((x**2 + y**2 + z**2)**(3.0/2.0)))*pos) | 
				
			||||||
 | 
					  h_acc_sym = imu_rot*(gravity + acceleration) | 
				
			||||||
 | 
					  h_phone_rot_sym = sp.Matrix([vroll, | 
				
			||||||
 | 
					                               vpitch, | 
				
			||||||
 | 
					                               vyaw]) | 
				
			||||||
 | 
					  speed = vx**2 + vy**2 + vz**2 | 
				
			||||||
 | 
					  h_speed_sym = sp.Matrix([sp.sqrt(speed)*odo_scale]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # orb stuff | 
				
			||||||
 | 
					  orb_pos_sym = sp.Matrix([orb_x - x, orb_y - y, orb_z - z]) | 
				
			||||||
 | 
					  orb_pos_rot_sym = quat_rot.T * orb_pos_sym | 
				
			||||||
 | 
					  s = orb_pos_rot_sym[0] | 
				
			||||||
 | 
					  h_orb_point_sym = sp.Matrix([(1/s)*(orb_pos_rot_sym[1]), | 
				
			||||||
 | 
					                               (1/s)*(orb_pos_rot_sym[2])]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_pos_sym = sp.Matrix([x, y, z]) | 
				
			||||||
 | 
					  h_imu_frame_sym = sp.Matrix(imu_angles) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h_relative_motion = sp.Matrix(quat_rot.T * v) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  obs_eqs = [[h_speed_sym, ObservationKind.ODOMETRIC_SPEED, None], | 
				
			||||||
 | 
					             [h_gyro_sym, ObservationKind.PHONE_GYRO, None], | 
				
			||||||
 | 
					             [h_phone_rot_sym, ObservationKind.NO_ROT, None], | 
				
			||||||
 | 
					             [h_acc_sym, ObservationKind.PHONE_ACCEL, None], | 
				
			||||||
 | 
					             [h_pseudorange_sym, ObservationKind.PSEUDORANGE_GPS, sat_pos_freq_sym], | 
				
			||||||
 | 
					             [h_pseudorange_glonass_sym, ObservationKind.PSEUDORANGE_GLONASS, sat_pos_freq_sym], | 
				
			||||||
 | 
					             [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GPS, sat_pos_vel_sym], | 
				
			||||||
 | 
					             [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GLONASS, sat_pos_vel_sym], | 
				
			||||||
 | 
					             [h_pos_sym, ObservationKind.ECEF_POS, None], | 
				
			||||||
 | 
					             [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], | 
				
			||||||
 | 
					             [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], | 
				
			||||||
 | 
					             [h_imu_frame_sym, ObservationKind.IMU_FRAME, None], | 
				
			||||||
 | 
					             [h_orb_point_sym, ObservationKind.ORB_POINT, orb_epos_sym]] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # MSCKF configuration | 
				
			||||||
 | 
					  if N > 0: | 
				
			||||||
 | 
					    focal_scale =1 | 
				
			||||||
 | 
					    # Add observation functions for orb feature tracks | 
				
			||||||
 | 
					    track_epos_sym = sp.MatrixSymbol('track_epos_sym', 3, 1) | 
				
			||||||
 | 
					    track_x, track_y, track_z = track_epos_sym | 
				
			||||||
 | 
					    h_track_sym = sp.Matrix(np.zeros(((1 + N)*2, 1))) | 
				
			||||||
 | 
					    track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) | 
				
			||||||
 | 
					    track_pos_rot_sym = quat_rot.T * track_pos_sym | 
				
			||||||
 | 
					    h_track_sym[-2:,:] = sp.Matrix([focal_scale*(track_pos_rot_sym[1]/track_pos_rot_sym[0]), | 
				
			||||||
 | 
					                                     focal_scale*(track_pos_rot_sym[2]/track_pos_rot_sym[0])]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    h_msckf_test_sym = sp.Matrix(np.zeros(((1 + N)*3, 1))) | 
				
			||||||
 | 
					    h_msckf_test_sym[-3:,:] = sp.Matrix([track_x - x,track_y - y , track_z - z]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for n in range(N): | 
				
			||||||
 | 
					      idx = dim_main + n*dim_augment | 
				
			||||||
 | 
					      err_idx = dim_main_err + n*dim_augment_err | 
				
			||||||
 | 
					      x, y, z = state[idx:idx+3] | 
				
			||||||
 | 
					      q = state[idx+3:idx+7] | 
				
			||||||
 | 
					      quat_rot = quat_rotate(*q) | 
				
			||||||
 | 
					      track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) | 
				
			||||||
 | 
					      track_pos_rot_sym = quat_rot.T * track_pos_sym | 
				
			||||||
 | 
					      h_track_sym[n*2:n*2+2,:] = sp.Matrix([focal_scale*(track_pos_rot_sym[1]/track_pos_rot_sym[0]), | 
				
			||||||
 | 
					                                             focal_scale*(track_pos_rot_sym[2]/track_pos_rot_sym[0])]) | 
				
			||||||
 | 
					      h_msckf_test_sym[n*3:n*3+3,:] = sp.Matrix([track_x - x, track_y - y, track_z - z]) | 
				
			||||||
 | 
					    obs_eqs.append([h_msckf_test_sym, ObservationKind.MSCKF_TEST, track_epos_sym]) | 
				
			||||||
 | 
					    obs_eqs.append([h_track_sym, ObservationKind.ORB_FEATURES, track_epos_sym]) | 
				
			||||||
 | 
					    obs_eqs.append([h_track_sym, ObservationKind.FEATURE_TRACK_TEST, track_epos_sym]) | 
				
			||||||
 | 
					    msckf_params = [dim_main, dim_augment, dim_main_err, dim_augment_err, N, [ObservationKind.MSCKF_TEST, ObservationKind.ORB_FEATURES]] | 
				
			||||||
 | 
					  else: | 
				
			||||||
 | 
					    msckf_params = None | 
				
			||||||
 | 
					  gen_code(name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state_err, eskf_params, msckf_params, maha_test_kinds) | 
				
			||||||
@ -0,0 +1,31 @@ | 
				
			|||||||
 | 
					import os | 
				
			||||||
 | 
					import subprocess | 
				
			||||||
 | 
					from common.basedir import BASEDIR | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cffi import FFI | 
				
			||||||
 | 
					from ctypes import cdll | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					locationd_dir = os.path.dirname(os.path.abspath(__file__)) | 
				
			||||||
 | 
					liblocationd_fn = os.path.join(locationd_dir, "liblocationd.so") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ffi = FFI() | 
				
			||||||
 | 
					ffi.cdef(""" | 
				
			||||||
 | 
					void *localizer_init(void); | 
				
			||||||
 | 
					void localizer_handle_log(void * localizer, const unsigned char * data, size_t len); | 
				
			||||||
 | 
					double localizer_get_yaw(void * localizer); | 
				
			||||||
 | 
					double localizer_get_bias(void * localizer); | 
				
			||||||
 | 
					double localizer_get_t(void * localizer); | 
				
			||||||
 | 
					void *params_learner_init(size_t len, char * params, double angle_offset, double stiffness_factor, double steer_ratio, double learning_rate); | 
				
			||||||
 | 
					bool params_learner_update(void * params_learner, double psi, double u, double sa); | 
				
			||||||
 | 
					double params_learner_get_ao(void * params_learner); | 
				
			||||||
 | 
					double params_learner_get_slow_ao(void * params_learner); | 
				
			||||||
 | 
					double params_learner_get_x(void * params_learner); | 
				
			||||||
 | 
					double params_learner_get_sR(void * params_learner); | 
				
			||||||
 | 
					double * localizer_get_P(void * localizer); | 
				
			||||||
 | 
					void localizer_set_P(void * localizer, double * P); | 
				
			||||||
 | 
					double * localizer_get_state(void * localizer); | 
				
			||||||
 | 
					void localizer_set_state(void * localizer, double * state); | 
				
			||||||
 | 
					""") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					liblocationd = ffi.dlopen(liblocationd_fn) | 
				
			||||||
@ -0,0 +1,232 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import zmq | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from bisect import bisect_right | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					from selfdrive.swaglog import cloudlog | 
				
			||||||
 | 
					from cereal.services import service_list | 
				
			||||||
 | 
					from common.transformations.orientation import rotations_from_quats, ecef_euler_from_ned, euler2quat, ned_euler_from_ecef, quat2euler | 
				
			||||||
 | 
					import common.transformations.coordinates as coord | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import laika.raw_gnss as gnss | 
				
			||||||
 | 
					from laika.astro_dog import AstroDog | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.loc_kf import LocKalman | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.kalman_helpers import ObservationKind | 
				
			||||||
 | 
					os.environ["OMP_NUM_THREADS"] = "1" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Localizer(): | 
				
			||||||
 | 
					  def __init__(self, disabled_logs=[], dog=None): | 
				
			||||||
 | 
					    self.kf = LocKalman(0) | 
				
			||||||
 | 
					    self.reset_kalman() | 
				
			||||||
 | 
					    if dog: | 
				
			||||||
 | 
					      self.dog = dog | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.dog = AstroDog(auto_update=True) | 
				
			||||||
 | 
					    self.max_age = .2  # seconds | 
				
			||||||
 | 
					    self.disabled_logs = disabled_logs | 
				
			||||||
 | 
					    self.week = None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def liveLocationMsg(self, time): | 
				
			||||||
 | 
					    fix = messaging.log.LiveLocationData.new_message() | 
				
			||||||
 | 
					    predicted_state = self.kf.x | 
				
			||||||
 | 
					    fix_ecef = predicted_state[0:3] | 
				
			||||||
 | 
					    fix_pos_geo = coord.ecef2geodetic(fix_ecef) | 
				
			||||||
 | 
					    fix.lat = float(fix_pos_geo[0]) | 
				
			||||||
 | 
					    fix.lon = float(fix_pos_geo[1]) | 
				
			||||||
 | 
					    fix.alt = float(fix_pos_geo[2]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fix.speed = float(np.linalg.norm(predicted_state[7:10])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    orientation_ned_euler = ned_euler_from_ecef(fix_ecef, quat2euler(predicted_state[3:7])) | 
				
			||||||
 | 
					    fix.roll = float(orientation_ned_euler[0]*180/np.pi) | 
				
			||||||
 | 
					    fix.pitch = float(orientation_ned_euler[1]*180/np.pi) | 
				
			||||||
 | 
					    fix.heading = float(orientation_ned_euler[2]*180/np.pi) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fix.gyro = [float(predicted_state[10]), float(predicted_state[11]), float(predicted_state[12])] | 
				
			||||||
 | 
					    fix.accel = [float(predicted_state[19]), float(predicted_state[20]), float(predicted_state[21])] | 
				
			||||||
 | 
					    local_vel = rotations_from_quats(predicted_state[3:7]).T.dot(predicted_state[7:10]) | 
				
			||||||
 | 
					    fix.pitchCalibration = float((np.arctan2(local_vel[2], local_vel[0]))*180/np.pi) | 
				
			||||||
 | 
					    fix.yawCalibration = float((np.arctan2(local_vel[1], local_vel[0]))*180/np.pi) | 
				
			||||||
 | 
					    fix.imuFrame = [(180/np.pi)*float(predicted_state[23]), (180/np.pi)*float(predicted_state[24]), (180/np.pi)*float(predicted_state[25])] | 
				
			||||||
 | 
					    return fix | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update_kalman(self, time, kind, meas): | 
				
			||||||
 | 
					    idx = bisect_right([x[0] for x in self.observation_buffer], time) | 
				
			||||||
 | 
					    self.observation_buffer.insert(idx, (time, kind, meas)) | 
				
			||||||
 | 
					    #print len(self.observation_buffer), idx, self.kf.filter.filter_time, time | 
				
			||||||
 | 
					    while self.observation_buffer[-1][0] - self.observation_buffer[0][0] > self.max_age: | 
				
			||||||
 | 
					      if self.filter_ready: | 
				
			||||||
 | 
					        self.kf.predict_and_observe(*self.observation_buffer.pop(0)) | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        self.observation_buffer.pop(0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_gps(self, log, current_time): | 
				
			||||||
 | 
					    self.converter = coord.LocalCoord.from_geodetic([log.gpsLocationExternal.latitude, log.gpsLocationExternal.longitude, log.gpsLocationExternal.altitude]) | 
				
			||||||
 | 
					    fix_ecef = self.converter.ned2ecef([0,0,0]) | 
				
			||||||
 | 
					    # initing with bad bearing allowed, maybe bad? | 
				
			||||||
 | 
					    if not self.filter_ready and len(list(self.dog.orbits.keys())) >6:  # and log.gpsLocationExternal.speed > 5: | 
				
			||||||
 | 
					      self.filter_ready = True | 
				
			||||||
 | 
					      initial_ecef = fix_ecef | 
				
			||||||
 | 
					      initial_state = np.zeros(29) | 
				
			||||||
 | 
					      gps_bearing = log.gpsLocationExternal.bearing*(np.pi/180) | 
				
			||||||
 | 
					      initial_pose_ecef = ecef_euler_from_ned(initial_ecef, [0, 0, gps_bearing]) | 
				
			||||||
 | 
					      initial_pose_ecef_quat = euler2quat(initial_pose_ecef) | 
				
			||||||
 | 
					      gps_speed = log.gpsLocationExternal.speed | 
				
			||||||
 | 
					      quat_uncertainty = 0.2**2 | 
				
			||||||
 | 
					      initial_pose_ecef_quat = euler2quat(initial_pose_ecef) | 
				
			||||||
 | 
					      initial_state[:3] = initial_ecef | 
				
			||||||
 | 
					      initial_state[3:7] = initial_pose_ecef_quat | 
				
			||||||
 | 
					      initial_state[7:10] = rotations_from_quats(initial_pose_ecef_quat).dot(np.array([gps_speed, 0, 0])) | 
				
			||||||
 | 
					      initial_state[18] = 1 | 
				
			||||||
 | 
					      initial_state[22] = 1 | 
				
			||||||
 | 
					      covs_diag = np.array([10**2,10**2,10**2, | 
				
			||||||
 | 
					                     quat_uncertainty, quat_uncertainty, quat_uncertainty, | 
				
			||||||
 | 
					                     2**2, 2**2, 2**2, | 
				
			||||||
 | 
					                     1, 1, 1, | 
				
			||||||
 | 
					                     20000000**2, 100**2, | 
				
			||||||
 | 
					                     0.01**2, 0.01**2, 0.01**2, | 
				
			||||||
 | 
					                     0.02**2, | 
				
			||||||
 | 
					                     2**2, 2**2, 2**2, | 
				
			||||||
 | 
					                     .01**2, | 
				
			||||||
 | 
					                     0.01**2, 0.01**2, 0.01**2, | 
				
			||||||
 | 
					                     10**2, 1**2, | 
				
			||||||
 | 
					                     0.2**2]) | 
				
			||||||
 | 
					      self.kf.init_state(initial_state, covs=np.diag(covs_diag), filter_time=current_time) | 
				
			||||||
 | 
					      print("Filter initialized") | 
				
			||||||
 | 
					    elif self.filter_ready: | 
				
			||||||
 | 
					      #self.update_kalman(current_time, ObservationKind.ECEF_POS, fix_ecef) | 
				
			||||||
 | 
					      gps_est_error = np.sqrt((self.kf.x[0] - fix_ecef[0])**2 + | 
				
			||||||
 | 
					                              (self.kf.x[1] - fix_ecef[1])**2 + | 
				
			||||||
 | 
					                              (self.kf.x[2] - fix_ecef[2])**2) | 
				
			||||||
 | 
					      if gps_est_error > 50: | 
				
			||||||
 | 
					        cloudlog.info("Locationd vs ubloxLocation difference too large, kalman reset") | 
				
			||||||
 | 
					        self.reset_kalman() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_car_state(self, log, current_time): | 
				
			||||||
 | 
					    self.speed_counter += 1 | 
				
			||||||
 | 
					    if self.speed_counter % 5==0: | 
				
			||||||
 | 
					      self.update_kalman(current_time, ObservationKind.ODOMETRIC_SPEED, log.carState.vEgo) | 
				
			||||||
 | 
					      if log.carState.vEgo == 0: | 
				
			||||||
 | 
					        self.update_kalman(current_time, ObservationKind.NO_ROT, [0, 0, 0]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_ublox_gnss(self, log, current_time): | 
				
			||||||
 | 
					    if hasattr(log.ubloxGnss, 'measurementReport'): | 
				
			||||||
 | 
					      self.raw_gnss_counter += 1 | 
				
			||||||
 | 
					      if True or self.raw_gnss_counter % 3==0: | 
				
			||||||
 | 
					        processed_raw = gnss.process_measurements(gnss.read_raw_ublox(log.ubloxGnss.measurementReport), dog=self.dog) | 
				
			||||||
 | 
					        corrected_raw = gnss.correct_measurements(processed_raw, self.kf.x[:3], dog=self.dog) | 
				
			||||||
 | 
					        corrected_raw = np.array([c.as_array() for c in corrected_raw]).reshape((-1,14)) | 
				
			||||||
 | 
					        self.update_kalman(current_time, ObservationKind.PSEUDORANGE_GPS, corrected_raw) | 
				
			||||||
 | 
					        self.update_kalman(current_time, ObservationKind.PSEUDORANGE_RATE_GPS, corrected_raw) | 
				
			||||||
 | 
					    #elif hasattr(log.ubloxGnss, 'ephemeris'): | 
				
			||||||
 | 
					    #  self.dog.add_ublox_ephems([log]) | 
				
			||||||
 | 
					    #  if len(self.dog.orbits.keys()) < 6: | 
				
			||||||
 | 
					    #    print 'Added ublox ephem now has ', len(self.dog.orbits.keys()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_qcom_gnss(self, log, current_time): | 
				
			||||||
 | 
					    if hasattr(log.qcomGnss, 'drSvPoly') and self.week is not None: | 
				
			||||||
 | 
					      self.dog.add_qcom_ephems([log], self.week) | 
				
			||||||
 | 
					      if len(list(self.dog.orbits.keys())) < 6: | 
				
			||||||
 | 
					        print('Added qcom ephem now has ', len(list(self.dog.orbits.keys()))) | 
				
			||||||
 | 
					    if hasattr(log.qcomGnss, 'drMeasurementReport') and log.qcomGnss.drMeasurementReport.source == "gps": | 
				
			||||||
 | 
					      self.week = log.qcomGnss.drMeasurementReport.gpsWeek | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_cam_odo(self, log, current_time): | 
				
			||||||
 | 
					    self.update_kalman(current_time, ObservationKind.CAMERA_ODO_ROTATION, np.concatenate([log.cameraOdometry.rot, | 
				
			||||||
 | 
					                                                                                          log.cameraOdometry.rotStd])) | 
				
			||||||
 | 
					    self.update_kalman(current_time, ObservationKind.CAMERA_ODO_TRANSLATION, np.concatenate([log.cameraOdometry.trans, | 
				
			||||||
 | 
					                                                                                             log.cameraOdometry.transStd])) | 
				
			||||||
 | 
					    pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_sensors(self, log, current_time): | 
				
			||||||
 | 
					    for sensor_reading in log.sensorEvents: | 
				
			||||||
 | 
					      # TODO does not yet account for double sensor readings in the log | 
				
			||||||
 | 
					      if sensor_reading.type == 4: | 
				
			||||||
 | 
					        self.gyro_counter += 1 | 
				
			||||||
 | 
					        if True or self.gyro_counter % 5==0: | 
				
			||||||
 | 
					          if max(abs(self.kf.x[23:26])) > 0.07: | 
				
			||||||
 | 
					            print('imu frame angles exceeded, correcting') | 
				
			||||||
 | 
					            self.update_kalman(current_time, ObservationKind.IMU_FRAME, [0, 0, 0]) | 
				
			||||||
 | 
					          self.update_kalman(current_time, ObservationKind.PHONE_GYRO, [-sensor_reading.gyro.v[2], -sensor_reading.gyro.v[1], -sensor_reading.gyro.v[0]]) | 
				
			||||||
 | 
					      if sensor_reading.type == 1: | 
				
			||||||
 | 
					        self.acc_counter += 1 | 
				
			||||||
 | 
					        if True or self.acc_counter % 5==0: | 
				
			||||||
 | 
					          self.update_kalman(current_time, ObservationKind.PHONE_ACCEL, [-sensor_reading.acceleration.v[2], -sensor_reading.acceleration.v[1], -sensor_reading.acceleration.v[0]]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_log(self, log): | 
				
			||||||
 | 
					    current_time = 1e-9*log.logMonoTime | 
				
			||||||
 | 
					    typ = log.which | 
				
			||||||
 | 
					    if typ in self.disabled_logs: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    if typ == "sensorEvents": | 
				
			||||||
 | 
					      self.handle_sensors(log, current_time) | 
				
			||||||
 | 
					    elif typ == "gpsLocationExternal": | 
				
			||||||
 | 
					      self.handle_gps(log, current_time) | 
				
			||||||
 | 
					    elif typ == "carState": | 
				
			||||||
 | 
					      self.handle_car_state(log, current_time) | 
				
			||||||
 | 
					    elif typ == "ubloxGnss": | 
				
			||||||
 | 
					      self.handle_ublox_gnss(log, current_time) | 
				
			||||||
 | 
					    elif typ == "qcomGnss": | 
				
			||||||
 | 
					      self.handle_qcom_gnss(log, current_time) | 
				
			||||||
 | 
					    elif typ == "cameraOdometry": | 
				
			||||||
 | 
					      self.handle_cam_odo(log, current_time) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def reset_kalman(self): | 
				
			||||||
 | 
					    self.filter_time = None | 
				
			||||||
 | 
					    self.filter_ready = False | 
				
			||||||
 | 
					    self.observation_buffer = [] | 
				
			||||||
 | 
					    self.converter = None | 
				
			||||||
 | 
					    self.gyro_counter = 0 | 
				
			||||||
 | 
					    self.acc_counter = 0 | 
				
			||||||
 | 
					    self.raw_gnss_counter = 0 | 
				
			||||||
 | 
					    self.speed_counter = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def locationd_thread(gctx, addr, disabled_logs): | 
				
			||||||
 | 
					  poller = zmq.Poller() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #carstate = messaging.sub_sock('carState', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  gpsLocationExternal = messaging.sub_sock('gpsLocationExternal', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  ubloxGnss = messaging.sub_sock('ubloxGnss', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  qcomGnss = messaging.sub_sock('qcomGnss', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  sensorEvents = messaging.sub_sock('sensorEvents', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  liveLocation = messaging.pub_sock('liveLocation') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  localizer = Localizer(disabled_logs=disabled_logs) | 
				
			||||||
 | 
					  print("init done") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # buffer with all the messages that still need to be input into the kalman | 
				
			||||||
 | 
					  while 1: | 
				
			||||||
 | 
					    polld = poller.poll(timeout=1000) | 
				
			||||||
 | 
					    for sock, mode in polld: | 
				
			||||||
 | 
					      if mode != zmq.POLLIN: | 
				
			||||||
 | 
					        continue | 
				
			||||||
 | 
					      logs = messaging.drain_sock(sock) | 
				
			||||||
 | 
					      for log in logs: | 
				
			||||||
 | 
					        localizer.handle_log(log) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if localizer.filter_ready and log.which == 'ubloxGnss': | 
				
			||||||
 | 
					      msg = messaging.new_message() | 
				
			||||||
 | 
					      msg.logMonoTime = log.logMonoTime | 
				
			||||||
 | 
					      msg.init('liveLocation') | 
				
			||||||
 | 
					      msg.liveLocation = localizer.liveLocationMsg(log.logMonoTime*1e-9) | 
				
			||||||
 | 
					      liveLocation.send(msg.to_bytes()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(gctx=None, addr="127.0.0.1"): | 
				
			||||||
 | 
					  IN_CAR = os.getenv("IN_CAR", False) | 
				
			||||||
 | 
					  disabled_logs = os.getenv("DISABLED_LOGS", "").split(",") | 
				
			||||||
 | 
					  # No speed for now | 
				
			||||||
 | 
					  disabled_logs.append('carState') | 
				
			||||||
 | 
					  if IN_CAR: | 
				
			||||||
 | 
					    addr = "192.168.5.11" | 
				
			||||||
 | 
					  locationd_thread(gctx, addr, disabled_logs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
@ -0,0 +1,206 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import zmq | 
				
			||||||
 | 
					import math | 
				
			||||||
 | 
					import json | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					from bisect import bisect_right | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cereal import car | 
				
			||||||
 | 
					from common.params import Params | 
				
			||||||
 | 
					from common.numpy_fast import clip | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					from selfdrive.swaglog import cloudlog | 
				
			||||||
 | 
					from selfdrive.controls.lib.vehicle_model import VehicleModel | 
				
			||||||
 | 
					from cereal.services import service_list | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.loc_local_kf import LocLocalKalman | 
				
			||||||
 | 
					from selfdrive.locationd.kalman.kalman_helpers import ObservationKind | 
				
			||||||
 | 
					from selfdrive.locationd.params_learner import ParamsLearner | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEBUG = False | 
				
			||||||
 | 
					kf = LocLocalKalman()  # Make sure that model is generated on import time | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LEARNING_RATE = 3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Localizer(): | 
				
			||||||
 | 
					  def __init__(self, disabled_logs=None, dog=None): | 
				
			||||||
 | 
					    self.kf = LocLocalKalman() | 
				
			||||||
 | 
					    self.reset_kalman() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.sensor_data_t = 0.0 | 
				
			||||||
 | 
					    self.max_age = .2  # seconds | 
				
			||||||
 | 
					    self.calibration_valid = False | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if disabled_logs is None: | 
				
			||||||
 | 
					      self.disabled_logs = list() | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.disabled_logs = disabled_logs | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def reset_kalman(self): | 
				
			||||||
 | 
					    self.filter_time = None | 
				
			||||||
 | 
					    self.observation_buffer = [] | 
				
			||||||
 | 
					    self.converter = None | 
				
			||||||
 | 
					    self.speed_counter = 0 | 
				
			||||||
 | 
					    self.sensor_counter = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def liveLocationMsg(self, time): | 
				
			||||||
 | 
					    fix = messaging.log.KalmanOdometry.new_message() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    predicted_state = self.kf.x | 
				
			||||||
 | 
					    fix.trans = [float(predicted_state[0]), float(predicted_state[1]), float(predicted_state[2])] | 
				
			||||||
 | 
					    fix.rot = [float(predicted_state[3]), float(predicted_state[4]), float(predicted_state[5])] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fix | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update_kalman(self, time, kind, meas): | 
				
			||||||
 | 
					    idx = bisect_right([x[0] for x in self.observation_buffer], time) | 
				
			||||||
 | 
					    self.observation_buffer.insert(idx, (time, kind, meas)) | 
				
			||||||
 | 
					    while self.observation_buffer[-1][0] - self.observation_buffer[0][0] > self.max_age: | 
				
			||||||
 | 
					      self.kf.predict_and_observe(*self.observation_buffer.pop(0)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_cam_odo(self, log, current_time): | 
				
			||||||
 | 
					    self.update_kalman(current_time, ObservationKind.CAMERA_ODO_ROTATION, np.concatenate([log.cameraOdometry.rot, | 
				
			||||||
 | 
					                                                                                          log.cameraOdometry.rotStd])) | 
				
			||||||
 | 
					    self.update_kalman(current_time, ObservationKind.CAMERA_ODO_TRANSLATION, np.concatenate([log.cameraOdometry.trans, | 
				
			||||||
 | 
					                                                                                             log.cameraOdometry.transStd])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_controls_state(self, log, current_time): | 
				
			||||||
 | 
					    self.speed_counter += 1 | 
				
			||||||
 | 
					    if self.speed_counter % 5 == 0: | 
				
			||||||
 | 
					      self.update_kalman(current_time, ObservationKind.ODOMETRIC_SPEED, np.array([log.controlsState.vEgo])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_sensors(self, log, current_time): | 
				
			||||||
 | 
					    for sensor_reading in log.sensorEvents: | 
				
			||||||
 | 
					      # TODO does not yet account for double sensor readings in the log | 
				
			||||||
 | 
					      if sensor_reading.type == 4: | 
				
			||||||
 | 
					        self.sensor_counter += 1 | 
				
			||||||
 | 
					        if self.sensor_counter % LEARNING_RATE == 0: | 
				
			||||||
 | 
					          self.update_kalman(current_time, ObservationKind.PHONE_GYRO, [-sensor_reading.gyro.v[2], -sensor_reading.gyro.v[1], -sensor_reading.gyro.v[0]]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_log(self, log): | 
				
			||||||
 | 
					    current_time = 1e-9 * log.logMonoTime | 
				
			||||||
 | 
					    typ = log.which | 
				
			||||||
 | 
					    if typ in self.disabled_logs: | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    if typ == "sensorEvents": | 
				
			||||||
 | 
					      self.sensor_data_t = current_time | 
				
			||||||
 | 
					      self.handle_sensors(log, current_time) | 
				
			||||||
 | 
					    elif typ == "controlsState": | 
				
			||||||
 | 
					      self.handle_controls_state(log, current_time) | 
				
			||||||
 | 
					    elif typ == "cameraOdometry": | 
				
			||||||
 | 
					      self.handle_cam_odo(log, current_time) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def locationd_thread(gctx, addr, disabled_logs): | 
				
			||||||
 | 
					  poller = zmq.Poller() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  controls_state_socket = messaging.sub_sock('controlsState', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  sensor_events_socket = messaging.sub_sock('sensorEvents', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					  camera_odometry_socket = messaging.sub_sock('cameraOdometry', poller, addr=addr, conflate=True) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  kalman_odometry_socket = messaging.pub_sock('kalmanOdometry') | 
				
			||||||
 | 
					  live_parameters_socket = messaging.pub_sock('liveParameters') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  params_reader = Params() | 
				
			||||||
 | 
					  cloudlog.info("Parameter learner is waiting for CarParams") | 
				
			||||||
 | 
					  CP = car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) | 
				
			||||||
 | 
					  VM = VehicleModel(CP) | 
				
			||||||
 | 
					  cloudlog.info("Parameter learner got CarParams: %s" % CP.carFingerprint) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  params = params_reader.get("LiveParameters") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Check if car model matches | 
				
			||||||
 | 
					  if params is not None: | 
				
			||||||
 | 
					    params = json.loads(params) | 
				
			||||||
 | 
					    if (params.get('carFingerprint', None) != CP.carFingerprint) or (params.get('carVin', CP.carVin) != CP.carVin): | 
				
			||||||
 | 
					      cloudlog.info("Parameter learner found parameters for wrong car.") | 
				
			||||||
 | 
					      params = None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if params is None: | 
				
			||||||
 | 
					    params = { | 
				
			||||||
 | 
					      'carFingerprint': CP.carFingerprint, | 
				
			||||||
 | 
					      'carVin': CP.carVin, | 
				
			||||||
 | 
					      'angleOffsetAverage': 0.0, | 
				
			||||||
 | 
					      'stiffnessFactor': 1.0, | 
				
			||||||
 | 
					      'steerRatio': VM.sR, | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    params_reader.put("LiveParameters", json.dumps(params)) | 
				
			||||||
 | 
					    cloudlog.info("Parameter learner resetting to default values") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cloudlog.info("Parameter starting with: %s" % str(params)) | 
				
			||||||
 | 
					  localizer = Localizer(disabled_logs=disabled_logs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  learner = ParamsLearner(VM, | 
				
			||||||
 | 
					                          angle_offset=params['angleOffsetAverage'], | 
				
			||||||
 | 
					                          stiffness_factor=params['stiffnessFactor'], | 
				
			||||||
 | 
					                          steer_ratio=params['steerRatio'], | 
				
			||||||
 | 
					                          learning_rate=LEARNING_RATE) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  i = 1 | 
				
			||||||
 | 
					  while True: | 
				
			||||||
 | 
					    for socket, event in poller.poll(timeout=1000): | 
				
			||||||
 | 
					      log = messaging.recv_one(socket) | 
				
			||||||
 | 
					      localizer.handle_log(log) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if socket is controls_state_socket: | 
				
			||||||
 | 
					        if not localizer.kf.t: | 
				
			||||||
 | 
					          continue | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if i % LEARNING_RATE == 0: | 
				
			||||||
 | 
					          # controlsState is not updating the Kalman Filter, so update KF manually | 
				
			||||||
 | 
					          localizer.kf.predict(1e-9 * log.logMonoTime) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          predicted_state = localizer.kf.x | 
				
			||||||
 | 
					          yaw_rate = -float(predicted_state[5]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          steering_angle = math.radians(log.controlsState.angleSteers) | 
				
			||||||
 | 
					          params_valid = learner.update(yaw_rate, log.controlsState.vEgo, steering_angle) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          log_t = 1e-9 * log.logMonoTime | 
				
			||||||
 | 
					          sensor_data_age = log_t - localizer.sensor_data_t | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          params = messaging.new_message() | 
				
			||||||
 | 
					          params.init('liveParameters') | 
				
			||||||
 | 
					          params.liveParameters.valid = bool(params_valid) | 
				
			||||||
 | 
					          params.liveParameters.sensorValid = bool(sensor_data_age < 5.0) | 
				
			||||||
 | 
					          params.liveParameters.angleOffset = float(math.degrees(learner.ao)) | 
				
			||||||
 | 
					          params.liveParameters.angleOffsetAverage = float(math.degrees(learner.slow_ao)) | 
				
			||||||
 | 
					          params.liveParameters.stiffnessFactor = float(learner.x) | 
				
			||||||
 | 
					          params.liveParameters.steerRatio = float(learner.sR) | 
				
			||||||
 | 
					          live_parameters_socket.send(params.to_bytes()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if i % 6000 == 0:   # once a minute | 
				
			||||||
 | 
					          params = learner.get_values() | 
				
			||||||
 | 
					          params['carFingerprint'] = CP.carFingerprint | 
				
			||||||
 | 
					          params['carVin'] = CP.carVin | 
				
			||||||
 | 
					          params_reader.put("LiveParameters", json.dumps(params)) | 
				
			||||||
 | 
					          params_reader.put("ControlsParams", json.dumps({'angle_model_bias': log.controlsState.angleModelBias})) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        i += 1 | 
				
			||||||
 | 
					      elif socket is camera_odometry_socket: | 
				
			||||||
 | 
					        msg = messaging.new_message() | 
				
			||||||
 | 
					        msg.init('kalmanOdometry') | 
				
			||||||
 | 
					        msg.logMonoTime = log.logMonoTime | 
				
			||||||
 | 
					        msg.kalmanOdometry = localizer.liveLocationMsg(log.logMonoTime * 1e-9) | 
				
			||||||
 | 
					        kalman_odometry_socket.send(msg.to_bytes()) | 
				
			||||||
 | 
					      elif socket is sensor_events_socket: | 
				
			||||||
 | 
					        pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(gctx=None, addr="127.0.0.1"): | 
				
			||||||
 | 
					  IN_CAR = os.getenv("IN_CAR", False) | 
				
			||||||
 | 
					  disabled_logs = os.getenv("DISABLED_LOGS", "").split(",") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # No speed for now | 
				
			||||||
 | 
					  disabled_logs.append('controlsState') | 
				
			||||||
 | 
					  if IN_CAR: | 
				
			||||||
 | 
					    addr = "192.168.5.11" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  locationd_thread(gctx, addr, disabled_logs) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
@ -0,0 +1,163 @@ | 
				
			|||||||
 | 
					#include <iostream> | 
				
			||||||
 | 
					#include <cmath> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <capnp/message.h> | 
				
			||||||
 | 
					#include <capnp/serialize-packed.h> | 
				
			||||||
 | 
					#include <eigen3/Eigen/Dense> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "locationd_yawrate.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Localizer::update_state(const Eigen::Matrix<double, 1, 4> &C, const double R, double current_time, double meas) { | 
				
			||||||
 | 
					  double dt = current_time - prev_update_time; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (dt < 0) { | 
				
			||||||
 | 
					    dt = 0; | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    prev_update_time = current_time; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  x = A * x; | 
				
			||||||
 | 
					  P = A * P * A.transpose() + dt * Q; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double y = meas - C * x; | 
				
			||||||
 | 
					  double S = R + C * P * C.transpose(); | 
				
			||||||
 | 
					  Eigen::Vector4d K = P * C.transpose() * (1.0 / S); | 
				
			||||||
 | 
					  x = x + K * y; | 
				
			||||||
 | 
					  P = (I - K * C) * P; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Localizer::handle_sensor_events(capnp::List<cereal::SensorEventData>::Reader sensor_events, double current_time) { | 
				
			||||||
 | 
					  for (cereal::SensorEventData::Reader sensor_event : sensor_events){ | 
				
			||||||
 | 
					    if (sensor_event.getSensor() == 5 && sensor_event.getType() == 16) { | 
				
			||||||
 | 
					      sensor_data_time = current_time; | 
				
			||||||
 | 
					      double meas = -sensor_event.getGyroUncalibrated().getV()[0]; | 
				
			||||||
 | 
					      update_state(C_gyro, R_gyro, current_time, meas); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Localizer::handle_camera_odometry(cereal::CameraOdometry::Reader camera_odometry, double current_time) { | 
				
			||||||
 | 
					  double R = pow(30.0 *camera_odometry.getRotStd()[2], 2); | 
				
			||||||
 | 
					  double meas = camera_odometry.getRot()[2]; | 
				
			||||||
 | 
					  update_state(C_posenet, R, current_time, meas); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto trans = camera_odometry.getTrans(); | 
				
			||||||
 | 
					  posenet_speed = sqrt(trans[0]*trans[0] + trans[1]*trans[1] + trans[2]*trans[2]); | 
				
			||||||
 | 
					  camera_odometry_time = current_time; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Localizer::handle_controls_state(cereal::ControlsState::Reader controls_state, double current_time) { | 
				
			||||||
 | 
					  steering_angle = controls_state.getAngleSteers() * DEGREES_TO_RADIANS; | 
				
			||||||
 | 
					  car_speed = controls_state.getVEgo(); | 
				
			||||||
 | 
					  controls_state_time = current_time; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Localizer::Localizer() { | 
				
			||||||
 | 
					  // States: [yaw rate, yaw rate diff, gyro bias, gyro bias diff]
 | 
				
			||||||
 | 
					  A << | 
				
			||||||
 | 
					    1, 1, 0, 0, | 
				
			||||||
 | 
					    0, 1, 0, 0, | 
				
			||||||
 | 
					    0, 0, 1, 1, | 
				
			||||||
 | 
					    0, 0, 0, 1; | 
				
			||||||
 | 
					  I << | 
				
			||||||
 | 
					    1, 0, 0, 0, | 
				
			||||||
 | 
					    0, 1, 0, 0, | 
				
			||||||
 | 
					    0, 0, 1, 0, | 
				
			||||||
 | 
					    0, 0, 0, 1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q << | 
				
			||||||
 | 
					    0, 0, 0, 0, | 
				
			||||||
 | 
					    0, pow(0.1, 2.0), 0, 0, | 
				
			||||||
 | 
					    0, 0, 0, 0, | 
				
			||||||
 | 
					    0, 0, pow(0.005 / 100.0, 2.0), 0; | 
				
			||||||
 | 
					  P << | 
				
			||||||
 | 
					    pow(100.0, 2.0), 0, 0, 0, | 
				
			||||||
 | 
					    0, pow(100.0, 2.0), 0, 0, | 
				
			||||||
 | 
					    0, 0, pow(100.0, 2.0), 0, | 
				
			||||||
 | 
					    0, 0, 0, pow(100.0, 2.0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  C_posenet << 1, 0, 0, 0; | 
				
			||||||
 | 
					  C_gyro << 1, 0, 1, 0; | 
				
			||||||
 | 
					  x << 0, 0, 0, 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  R_gyro = pow(0.25, 2.0); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Localizer::handle_log(cereal::Event::Reader event) { | 
				
			||||||
 | 
					  double current_time = event.getLogMonoTime() / 1.0e9; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Initialize update_time on first update
 | 
				
			||||||
 | 
					  if (prev_update_time < 0) { | 
				
			||||||
 | 
					    prev_update_time = current_time; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto type = event.which(); | 
				
			||||||
 | 
					  switch(type) { | 
				
			||||||
 | 
					  case cereal::Event::CONTROLS_STATE: | 
				
			||||||
 | 
					    handle_controls_state(event.getControlsState(), current_time); | 
				
			||||||
 | 
					    break; | 
				
			||||||
 | 
					  case cereal::Event::CAMERA_ODOMETRY: | 
				
			||||||
 | 
					    handle_camera_odometry(event.getCameraOdometry(), current_time); | 
				
			||||||
 | 
					    break; | 
				
			||||||
 | 
					  case cereal::Event::SENSOR_EVENTS: | 
				
			||||||
 | 
					    handle_sensor_events(event.getSensorEvents(), current_time); | 
				
			||||||
 | 
					    break; | 
				
			||||||
 | 
					  default: | 
				
			||||||
 | 
					    break; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern "C" { | 
				
			||||||
 | 
					  void *localizer_init(void) { | 
				
			||||||
 | 
					    Localizer * localizer = new Localizer; | 
				
			||||||
 | 
					    return (void*)localizer; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void localizer_handle_log(void * localizer, const unsigned char * data, size_t len) { | 
				
			||||||
 | 
					    const kj::ArrayPtr<const capnp::word> view((const capnp::word*)data, len); | 
				
			||||||
 | 
					    capnp::FlatArrayMessageReader msg(view); | 
				
			||||||
 | 
					    cereal::Event::Reader event = msg.getRoot<cereal::Event>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    loc->handle_log(event); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double localizer_get_yaw(void * localizer) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    return loc->x[0]; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  double localizer_get_bias(void * localizer) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    return loc->x[2]; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double * localizer_get_state(void * localizer) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    return loc->x.data(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void localizer_set_state(void * localizer, double * state) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    memcpy(loc->x.data(), state, 4 * sizeof(double)); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double localizer_get_t(void * localizer) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    return loc->prev_update_time; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double * localizer_get_P(void * localizer) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    return loc->P.data(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void localizer_set_P(void * localizer, double * P) { | 
				
			||||||
 | 
					    Localizer * loc = (Localizer*) localizer; | 
				
			||||||
 | 
					    memcpy(loc->P.data(), P, 16 * sizeof(double)); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,37 @@ | 
				
			|||||||
 | 
					#pragma once | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <eigen3/Eigen/Dense> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEGREES_TO_RADIANS 0.017453292519943295 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Localizer | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					  Eigen::Matrix4d A; | 
				
			||||||
 | 
					  Eigen::Matrix4d I; | 
				
			||||||
 | 
					  Eigen::Matrix4d Q; | 
				
			||||||
 | 
					  Eigen::Matrix<double, 1, 4> C_posenet; | 
				
			||||||
 | 
					  Eigen::Matrix<double, 1, 4> C_gyro; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double R_gyro; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void update_state(const Eigen::Matrix<double, 1, 4> &C, const double R, double current_time, double meas); | 
				
			||||||
 | 
					  void handle_sensor_events(capnp::List<cereal::SensorEventData>::Reader sensor_events, double current_time); | 
				
			||||||
 | 
					  void handle_camera_odometry(cereal::CameraOdometry::Reader camera_odometry, double current_time); | 
				
			||||||
 | 
					  void handle_controls_state(cereal::ControlsState::Reader controls_state, double current_time); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  Eigen::Vector4d x; | 
				
			||||||
 | 
					  Eigen::Matrix4d P; | 
				
			||||||
 | 
					  double steering_angle = 0; | 
				
			||||||
 | 
					  double car_speed = 0; | 
				
			||||||
 | 
					  double posenet_speed = 0; | 
				
			||||||
 | 
					  double prev_update_time = -1; | 
				
			||||||
 | 
					  double controls_state_time = -1; | 
				
			||||||
 | 
					  double sensor_data_time = -1; | 
				
			||||||
 | 
					  double camera_odometry_time = -1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Localizer(); | 
				
			||||||
 | 
					  void handle_log(cereal::Event::Reader event); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}; | 
				
			||||||
@ -0,0 +1,118 @@ | 
				
			|||||||
 | 
					#include <algorithm> | 
				
			||||||
 | 
					#include <cmath> | 
				
			||||||
 | 
					#include <iostream> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <capnp/message.h> | 
				
			||||||
 | 
					#include <capnp/serialize-packed.h> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					#include "cereal/gen/cpp/car.capnp.h" | 
				
			||||||
 | 
					#include "params_learner.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #define DEBUG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T> | 
				
			||||||
 | 
					T clip(const T& n, const T& lower, const T& upper) { | 
				
			||||||
 | 
					  return std::max(lower, std::min(n, upper)); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ParamsLearner::ParamsLearner(cereal::CarParams::Reader car_params, | 
				
			||||||
 | 
					                             double angle_offset, | 
				
			||||||
 | 
					                             double stiffness_factor, | 
				
			||||||
 | 
					                             double steer_ratio, | 
				
			||||||
 | 
					                             double learning_rate) : | 
				
			||||||
 | 
					  ao(angle_offset * DEGREES_TO_RADIANS), | 
				
			||||||
 | 
					  slow_ao(angle_offset * DEGREES_TO_RADIANS), | 
				
			||||||
 | 
					  x(stiffness_factor), | 
				
			||||||
 | 
					  sR(steer_ratio) { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cF0 = car_params.getTireStiffnessFront(); | 
				
			||||||
 | 
					  cR0 = car_params.getTireStiffnessRear(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  l = car_params.getWheelbase(); | 
				
			||||||
 | 
					  m = car_params.getMass(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  aF = car_params.getCenterToFront(); | 
				
			||||||
 | 
					  aR = l - aF; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  min_sr = MIN_SR * car_params.getSteerRatio(); | 
				
			||||||
 | 
					  max_sr = MAX_SR * car_params.getSteerRatio(); | 
				
			||||||
 | 
					  min_sr_th = MIN_SR_TH * car_params.getSteerRatio(); | 
				
			||||||
 | 
					  max_sr_th = MAX_SR_TH * car_params.getSteerRatio(); | 
				
			||||||
 | 
					  alpha1 = 0.01 * learning_rate; | 
				
			||||||
 | 
					  alpha2 = 0.0005 * learning_rate; | 
				
			||||||
 | 
					  alpha3 = 0.1 * learning_rate; | 
				
			||||||
 | 
					  alpha4 = 1.0 * learning_rate; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ParamsLearner::update(double psi, double u, double sa) { | 
				
			||||||
 | 
					  if (u > 10.0 && fabs(sa) < (DEGREES_TO_RADIANS * 90.)) { | 
				
			||||||
 | 
					    double ao_diff = 2.0*cF0*cR0*l*u*x*(1.0*cF0*cR0*l*u*x*(ao - sa) + psi*sR*(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0)))/(pow(sR, 2)*pow(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0), 2)); | 
				
			||||||
 | 
					    double new_ao = ao - alpha1 * ao_diff; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double slow_ao_diff = 2.0*cF0*cR0*l*u*x*(1.0*cF0*cR0*l*u*x*(slow_ao - sa) + psi*sR*(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0)))/(pow(sR, 2)*pow(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0), 2)); | 
				
			||||||
 | 
					    double new_slow_ao = slow_ao - alpha2 * slow_ao_diff; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double new_x = x - alpha3 * (-2.0*cF0*cR0*l*m*pow(u, 3)*(slow_ao - sa)*(aF*cF0 - aR*cR0)*(1.0*cF0*cR0*l*u*x*(slow_ao - sa) + psi*sR*(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0)))/(pow(sR, 2)*pow(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0), 3))); | 
				
			||||||
 | 
					    double new_sR = sR - alpha4 * (-2.0*cF0*cR0*l*u*x*(slow_ao - sa)*(1.0*cF0*cR0*l*u*x*(slow_ao - sa) + psi*sR*(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0)))/(pow(sR, 3)*pow(cF0*cR0*pow(l, 2)*x - m*pow(u, 2)*(aF*cF0 - aR*cR0), 2))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ao = new_ao; | 
				
			||||||
 | 
					    slow_ao = new_slow_ao; | 
				
			||||||
 | 
					    x = new_x; | 
				
			||||||
 | 
					    sR = new_sR; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef DEBUG | 
				
			||||||
 | 
					  std::cout << "Instant AO: " << (RADIANS_TO_DEGREES * ao) << "\tAverage AO: " << (RADIANS_TO_DEGREES * slow_ao); | 
				
			||||||
 | 
					  std::cout << "\tStiffness: " << x << "\t sR: " << sR << std::endl; | 
				
			||||||
 | 
					#endif | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ao = clip(ao, -MAX_ANGLE_OFFSET, MAX_ANGLE_OFFSET); | 
				
			||||||
 | 
					  slow_ao = clip(slow_ao, -MAX_ANGLE_OFFSET, MAX_ANGLE_OFFSET); | 
				
			||||||
 | 
					  x = clip(x, MIN_STIFFNESS, MAX_STIFFNESS); | 
				
			||||||
 | 
					  sR = clip(sR, min_sr, max_sr); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool valid = fabs(slow_ao) < MAX_ANGLE_OFFSET_TH; | 
				
			||||||
 | 
					  valid = valid && sR > min_sr_th; | 
				
			||||||
 | 
					  valid = valid && sR < max_sr_th; | 
				
			||||||
 | 
					  return valid; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern "C" { | 
				
			||||||
 | 
					  void *params_learner_init(size_t len, char * params, double angle_offset, double stiffness_factor, double steer_ratio, double learning_rate) { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto amsg = kj::heapArray<capnp::word>((len / sizeof(capnp::word)) + 1); | 
				
			||||||
 | 
					    memcpy(amsg.begin(), params, len); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    capnp::FlatArrayMessageReader cmsg(amsg); | 
				
			||||||
 | 
					    cereal::CarParams::Reader car_params = cmsg.getRoot<cereal::CarParams>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ParamsLearner * p = new ParamsLearner(car_params, angle_offset, stiffness_factor, steer_ratio, learning_rate); | 
				
			||||||
 | 
					    return (void*)p; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool params_learner_update(void * params_learner, double psi, double u, double sa) { | 
				
			||||||
 | 
					    ParamsLearner * p = (ParamsLearner*) params_learner; | 
				
			||||||
 | 
					    return p->update(psi, u, sa); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double params_learner_get_ao(void * params_learner){ | 
				
			||||||
 | 
					    ParamsLearner * p = (ParamsLearner*) params_learner; | 
				
			||||||
 | 
					    return p->ao; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double params_learner_get_x(void * params_learner){ | 
				
			||||||
 | 
					    ParamsLearner * p = (ParamsLearner*) params_learner; | 
				
			||||||
 | 
					    return p->x; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double params_learner_get_slow_ao(void * params_learner){ | 
				
			||||||
 | 
					    ParamsLearner * p = (ParamsLearner*) params_learner; | 
				
			||||||
 | 
					    return p->slow_ao; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double params_learner_get_sR(void * params_learner){ | 
				
			||||||
 | 
					    ParamsLearner * p = (ParamsLearner*) params_learner; | 
				
			||||||
 | 
					    return p->sR; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,35 @@ | 
				
			|||||||
 | 
					#pragma once | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEGREES_TO_RADIANS 0.017453292519943295 | 
				
			||||||
 | 
					#define RADIANS_TO_DEGREES (1.0 / DEGREES_TO_RADIANS) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_ANGLE_OFFSET (10.0 * DEGREES_TO_RADIANS) | 
				
			||||||
 | 
					#define MAX_ANGLE_OFFSET_TH  (9.0 * DEGREES_TO_RADIANS) | 
				
			||||||
 | 
					#define MIN_STIFFNESS  0.5 | 
				
			||||||
 | 
					#define MAX_STIFFNESS  2.0 | 
				
			||||||
 | 
					#define MIN_SR  0.5 | 
				
			||||||
 | 
					#define MAX_SR  2.0 | 
				
			||||||
 | 
					#define MIN_SR_TH  0.55 | 
				
			||||||
 | 
					#define MAX_SR_TH  1.9 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ParamsLearner { | 
				
			||||||
 | 
					  double cF0, cR0; | 
				
			||||||
 | 
					  double aR, aF; | 
				
			||||||
 | 
					  double l, m; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double min_sr, max_sr, min_sr_th, max_sr_th; | 
				
			||||||
 | 
					  double alpha1, alpha2, alpha3, alpha4; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  double ao; | 
				
			||||||
 | 
					  double slow_ao; | 
				
			||||||
 | 
					  double x, sR; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ParamsLearner(cereal::CarParams::Reader car_params, | 
				
			||||||
 | 
					                double angle_offset, | 
				
			||||||
 | 
					                double stiffness_factor, | 
				
			||||||
 | 
					                double steer_ratio, | 
				
			||||||
 | 
					                double learning_rate); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool update(double psi, double u, double sa); | 
				
			||||||
 | 
					}; | 
				
			||||||
@ -0,0 +1,84 @@ | 
				
			|||||||
 | 
					import math | 
				
			||||||
 | 
					from common.numpy_fast import clip | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MAX_ANGLE_OFFSET = math.radians(10.) | 
				
			||||||
 | 
					MAX_ANGLE_OFFSET_TH = math.radians(9.) | 
				
			||||||
 | 
					MIN_STIFFNESS = 0.5 | 
				
			||||||
 | 
					MAX_STIFFNESS = 2.0 | 
				
			||||||
 | 
					MIN_SR = 0.5 | 
				
			||||||
 | 
					MAX_SR = 2.0 | 
				
			||||||
 | 
					MIN_SR_TH = 0.55 | 
				
			||||||
 | 
					MAX_SR_TH = 1.9 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEBUG = False | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ParamsLearner(): | 
				
			||||||
 | 
					  def __init__(self, VM, angle_offset=0., stiffness_factor=1.0, steer_ratio=None, learning_rate=1.0): | 
				
			||||||
 | 
					    self.VM = VM | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.ao = math.radians(angle_offset) | 
				
			||||||
 | 
					    self.slow_ao = math.radians(angle_offset) | 
				
			||||||
 | 
					    self.x = stiffness_factor | 
				
			||||||
 | 
					    self.sR = VM.sR if steer_ratio is None else steer_ratio | 
				
			||||||
 | 
					    self.MIN_SR = MIN_SR * self.VM.sR | 
				
			||||||
 | 
					    self.MAX_SR = MAX_SR * self.VM.sR | 
				
			||||||
 | 
					    self.MIN_SR_TH = MIN_SR_TH * self.VM.sR | 
				
			||||||
 | 
					    self.MAX_SR_TH = MAX_SR_TH * self.VM.sR | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.alpha1 = 0.01 * learning_rate | 
				
			||||||
 | 
					    self.alpha2 = 0.0005 * learning_rate | 
				
			||||||
 | 
					    self.alpha3 = 0.1 * learning_rate | 
				
			||||||
 | 
					    self.alpha4 = 1.0 * learning_rate | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def get_values(self): | 
				
			||||||
 | 
					    return { | 
				
			||||||
 | 
					      'angleOffsetAverage': math.degrees(self.slow_ao), | 
				
			||||||
 | 
					      'stiffnessFactor': self.x, | 
				
			||||||
 | 
					      'steerRatio': self.sR, | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def update(self, psi, u, sa): | 
				
			||||||
 | 
					    cF0 = self.VM.cF | 
				
			||||||
 | 
					    cR0 = self.VM.cR | 
				
			||||||
 | 
					    aR = self.VM.aR | 
				
			||||||
 | 
					    aF = self.VM.aF | 
				
			||||||
 | 
					    l = self.VM.l | 
				
			||||||
 | 
					    m = self.VM.m | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    x = self.x | 
				
			||||||
 | 
					    ao = self.ao | 
				
			||||||
 | 
					    sR = self.sR | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Gradient descent:  learn angle offset, tire stiffness and steer ratio. | 
				
			||||||
 | 
					    if u > 10.0 and abs(math.degrees(sa)) < 15.: | 
				
			||||||
 | 
					      self.ao -= self.alpha1 * 2.0*cF0*cR0*l*u*x*(1.0*cF0*cR0*l*u*x*(ao - sa) + psi*sR*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0)))/(sR**2*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0))**2) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ao = self.slow_ao | 
				
			||||||
 | 
					      self.slow_ao -= self.alpha2 * 2.0*cF0*cR0*l*u*x*(1.0*cF0*cR0*l*u*x*(ao - sa) + psi*sR*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0)))/(sR**2*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0))**2) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.x -= self.alpha3 * -2.0*cF0*cR0*l*m*u**3*(ao - sa)*(aF*cF0 - aR*cR0)*(1.0*cF0*cR0*l*u*x*(ao - sa) + psi*sR*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0)))/(sR**2*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0))**3) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.sR -= self.alpha4 * -2.0*cF0*cR0*l*u*x*(ao - sa)*(1.0*cF0*cR0*l*u*x*(ao - sa) + psi*sR*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0)))/(sR**3*(cF0*cR0*l**2*x - m*u**2*(aF*cF0 - aR*cR0))**2) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if DEBUG: | 
				
			||||||
 | 
					      # s1 = "Measured yaw rate % .6f" % psi | 
				
			||||||
 | 
					      # ao = 0. | 
				
			||||||
 | 
					      # s2 = "Uncompensated yaw % .6f" % (1.0*u*(-ao + sa)/(l*sR*(1 - m*u**2*(aF*cF0*x - aR*cR0*x)/(cF0*cR0*l**2*x**2)))) | 
				
			||||||
 | 
					      # instant_ao = aF*m*psi*sR*u/(cR0*l*x) - aR*m*psi*sR*u/(cF0*l*x) - l*psi*sR/u + sa | 
				
			||||||
 | 
					      s4 = "Instant AO: % .6f Avg. AO % .6f" % (math.degrees(self.ao), math.degrees(self.slow_ao)) | 
				
			||||||
 | 
					      s5 = "Stiffnes: % .6f x" % self.x | 
				
			||||||
 | 
					      s6 = "sR: %.4f" % self.sR | 
				
			||||||
 | 
					      print("{0} {1} {2}".format(s4, s5, s6)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.ao = clip(self.ao, -MAX_ANGLE_OFFSET, MAX_ANGLE_OFFSET) | 
				
			||||||
 | 
					    self.slow_ao = clip(self.slow_ao, -MAX_ANGLE_OFFSET, MAX_ANGLE_OFFSET) | 
				
			||||||
 | 
					    self.x = clip(self.x, MIN_STIFFNESS, MAX_STIFFNESS) | 
				
			||||||
 | 
					    self.sR = clip(self.sR, self.MIN_SR, self.MAX_SR) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # don't check stiffness for validity, as it can change quickly if sR is off | 
				
			||||||
 | 
					    valid = abs(self.slow_ao) < MAX_ANGLE_OFFSET_TH and \ | 
				
			||||||
 | 
					      self.sR > self.MIN_SR_TH and self.sR < self.MAX_SR_TH | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return valid | 
				
			||||||
@ -0,0 +1,186 @@ | 
				
			|||||||
 | 
					#include <future> | 
				
			||||||
 | 
					#include <iostream> | 
				
			||||||
 | 
					#include <cassert> | 
				
			||||||
 | 
					#include <csignal> | 
				
			||||||
 | 
					#include <unistd.h> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <capnp/message.h> | 
				
			||||||
 | 
					#include <capnp/serialize-packed.h> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "json11.hpp" | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/swaglog.h" | 
				
			||||||
 | 
					#include "common/messaging.h" | 
				
			||||||
 | 
					#include "common/params.h" | 
				
			||||||
 | 
					#include "common/timing.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "messaging.hpp" | 
				
			||||||
 | 
					#include "locationd_yawrate.h" | 
				
			||||||
 | 
					#include "params_learner.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void sigpipe_handler(int sig) { | 
				
			||||||
 | 
					  LOGE("SIGPIPE received"); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char *argv[]) { | 
				
			||||||
 | 
					  signal(SIGPIPE, (sighandler_t)sigpipe_handler); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Context * c = Context::create(); | 
				
			||||||
 | 
					  SubSocket * controls_state_sock = SubSocket::create(c, "controlsState"); | 
				
			||||||
 | 
					  SubSocket * sensor_events_sock = SubSocket::create(c, "sensorEvents"); | 
				
			||||||
 | 
					  SubSocket * camera_odometry_sock = SubSocket::create(c, "cameraOdometry"); | 
				
			||||||
 | 
					  PubSocket * live_parameters_sock = PubSocket::create(c, "liveParameters"); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  assert(controls_state_sock != NULL); | 
				
			||||||
 | 
					  assert(sensor_events_sock != NULL); | 
				
			||||||
 | 
					  assert(camera_odometry_sock != NULL); | 
				
			||||||
 | 
					  assert(live_parameters_sock != NULL); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Poller * poller = Poller::create({controls_state_sock, sensor_events_sock, camera_odometry_sock}); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Localizer localizer; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Read car params
 | 
				
			||||||
 | 
					  char *value; | 
				
			||||||
 | 
					  size_t value_sz = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LOGW("waiting for params to set vehicle model"); | 
				
			||||||
 | 
					  while (true) { | 
				
			||||||
 | 
					    read_db_value(NULL, "CarParams", &value, &value_sz); | 
				
			||||||
 | 
					    if (value_sz > 0) break; | 
				
			||||||
 | 
					    usleep(100*1000); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  LOGW("got %d bytes CarParams", value_sz); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // make copy due to alignment issues
 | 
				
			||||||
 | 
					  auto amsg = kj::heapArray<capnp::word>((value_sz / sizeof(capnp::word)) + 1); | 
				
			||||||
 | 
					  memcpy(amsg.begin(), value, value_sz); | 
				
			||||||
 | 
					  free(value); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  capnp::FlatArrayMessageReader cmsg(amsg); | 
				
			||||||
 | 
					  cereal::CarParams::Reader car_params = cmsg.getRoot<cereal::CarParams>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Read params from previous run
 | 
				
			||||||
 | 
					  const int result = read_db_value(NULL, "LiveParameters", &value, &value_sz); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::string fingerprint = car_params.getCarFingerprint(); | 
				
			||||||
 | 
					  std::string vin = car_params.getCarVin(); | 
				
			||||||
 | 
					  double sR = car_params.getSteerRatio(); | 
				
			||||||
 | 
					  double x = 1.0; | 
				
			||||||
 | 
					  double ao = 0.0; | 
				
			||||||
 | 
					  double posenet_invalid_count = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (result == 0){ | 
				
			||||||
 | 
					    auto str = std::string(value, value_sz); | 
				
			||||||
 | 
					    free(value); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string err; | 
				
			||||||
 | 
					    auto json = json11::Json::parse(str, err); | 
				
			||||||
 | 
					    if (json.is_null() || !err.empty()) { | 
				
			||||||
 | 
					      std::string log = "Error parsing json: " + err; | 
				
			||||||
 | 
					      LOGW(log.c_str()); | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      std::string new_fingerprint = json["carFingerprint"].string_value(); | 
				
			||||||
 | 
					      std::string new_vin = json["carVin"].string_value(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (fingerprint == new_fingerprint && vin == new_vin) { | 
				
			||||||
 | 
					        std::string log = "Parameter starting with: " + str; | 
				
			||||||
 | 
					        LOGW(log.c_str()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sR = json["steerRatio"].number_value(); | 
				
			||||||
 | 
					        x = json["stiffnessFactor"].number_value(); | 
				
			||||||
 | 
					        ao = json["angleOffsetAverage"].number_value(); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ParamsLearner learner(car_params, ao, x, sR, 1.0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Main loop
 | 
				
			||||||
 | 
					  int save_counter = 0; | 
				
			||||||
 | 
					  while (true){ | 
				
			||||||
 | 
					    for (auto s : poller->poll(100)){ | 
				
			||||||
 | 
					      Message * msg = s->receive(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      auto amsg = kj::heapArray<capnp::word>((msg->getSize() / sizeof(capnp::word)) + 1); | 
				
			||||||
 | 
					      memcpy(amsg.begin(), msg->getData(), msg->getSize()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      capnp::FlatArrayMessageReader capnp_msg(amsg); | 
				
			||||||
 | 
					      cereal::Event::Reader event = capnp_msg.getRoot<cereal::Event>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      localizer.handle_log(event); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      auto which = event.which(); | 
				
			||||||
 | 
					      // Throw vision failure if posenet and odometric speed too different
 | 
				
			||||||
 | 
					      if (which == cereal::Event::CAMERA_ODOMETRY){ | 
				
			||||||
 | 
					        if (std::abs(localizer.posenet_speed - localizer.car_speed) > std::max(0.4 * localizer.car_speed, 5.0)) { | 
				
			||||||
 | 
					          posenet_invalid_count++; | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					          posenet_invalid_count = 0; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } else if (which == cereal::Event::CONTROLS_STATE){ | 
				
			||||||
 | 
					        save_counter++; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        double yaw_rate = -localizer.x[0]; | 
				
			||||||
 | 
					        bool valid = learner.update(yaw_rate, localizer.car_speed, localizer.steering_angle); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Fix in replay
 | 
				
			||||||
 | 
					        double sensor_data_age = localizer.controls_state_time - localizer.sensor_data_time; | 
				
			||||||
 | 
					        double camera_odometry_age = localizer.controls_state_time - localizer.camera_odometry_time; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        double angle_offset_degrees = RADIANS_TO_DEGREES * learner.ao; | 
				
			||||||
 | 
					        double angle_offset_average_degrees = RADIANS_TO_DEGREES * learner.slow_ao; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        capnp::MallocMessageBuilder msg; | 
				
			||||||
 | 
					        cereal::Event::Builder event = msg.initRoot<cereal::Event>(); | 
				
			||||||
 | 
					        event.setLogMonoTime(nanos_since_boot()); | 
				
			||||||
 | 
					        auto live_params = event.initLiveParameters(); | 
				
			||||||
 | 
					        live_params.setValid(valid); | 
				
			||||||
 | 
					        live_params.setYawRate(localizer.x[0]); | 
				
			||||||
 | 
					        live_params.setGyroBias(localizer.x[2]); | 
				
			||||||
 | 
					        live_params.setSensorValid(sensor_data_age < 5.0); | 
				
			||||||
 | 
					        live_params.setAngleOffset(angle_offset_degrees); | 
				
			||||||
 | 
					        live_params.setAngleOffsetAverage(angle_offset_average_degrees); | 
				
			||||||
 | 
					        live_params.setStiffnessFactor(learner.x); | 
				
			||||||
 | 
					        live_params.setSteerRatio(learner.sR); | 
				
			||||||
 | 
					        live_params.setPosenetSpeed(localizer.posenet_speed); | 
				
			||||||
 | 
					        live_params.setPosenetValid((posenet_invalid_count < 4) && (camera_odometry_age < 5.0)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto words = capnp::messageToFlatArray(msg); | 
				
			||||||
 | 
					        auto bytes = words.asBytes(); | 
				
			||||||
 | 
					        live_parameters_sock->send((char*)bytes.begin(), bytes.size()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Save parameters every minute
 | 
				
			||||||
 | 
					        if (save_counter % 6000 == 0) { | 
				
			||||||
 | 
					          json11::Json json = json11::Json::object { | 
				
			||||||
 | 
					                                                    {"carVin", vin}, | 
				
			||||||
 | 
					                                                    {"carFingerprint", fingerprint}, | 
				
			||||||
 | 
					                                                    {"steerRatio", learner.sR}, | 
				
			||||||
 | 
					                                                    {"stiffnessFactor", learner.x}, | 
				
			||||||
 | 
					                                                    {"angleOffsetAverage", angle_offset_average_degrees}, | 
				
			||||||
 | 
					          }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          std::string out = json.dump(); | 
				
			||||||
 | 
					          std::async(std::launch::async, | 
				
			||||||
 | 
					                     [out]{ | 
				
			||||||
 | 
					                       write_db_value(NULL, "LiveParameters", out.c_str(), out.length()); | 
				
			||||||
 | 
					                     }); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      delete msg; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete live_parameters_sock; | 
				
			||||||
 | 
					  delete controls_state_sock; | 
				
			||||||
 | 
					  delete camera_odometry_sock; | 
				
			||||||
 | 
					  delete sensor_events_sock; | 
				
			||||||
 | 
					  delete poller; | 
				
			||||||
 | 
					  delete c; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 0; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1 @@ | 
				
			|||||||
 | 
					out/ | 
				
			||||||
@ -0,0 +1,79 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import subprocess | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import sys | 
				
			||||||
 | 
					import argparse | 
				
			||||||
 | 
					import tempfile | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.locationd.test.ubloxd_py_test import parser_test | 
				
			||||||
 | 
					from selfdrive.locationd.test.ubloxd_regression_test import compare_results | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mkdirs_exists_ok(path): | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    os.makedirs(path) | 
				
			||||||
 | 
					  except OSError: | 
				
			||||||
 | 
					    if not os.path.isdir(path): | 
				
			||||||
 | 
					      raise | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(args): | 
				
			||||||
 | 
					  cur_dir = os.path.dirname(os.path.realpath(__file__)) | 
				
			||||||
 | 
					  ubloxd_dir = os.path.join(cur_dir, '../') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cc_output_dir = os.path.join(args.output_dir, 'cc') | 
				
			||||||
 | 
					  mkdirs_exists_ok(cc_output_dir) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  py_output_dir = os.path.join(args.output_dir, 'py') | 
				
			||||||
 | 
					  mkdirs_exists_ok(py_output_dir) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  archive_file = os.path.join(cur_dir, args.stream_gz_file) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    print('Extracting stream file') | 
				
			||||||
 | 
					    subprocess.check_call(['tar', 'zxf', archive_file], cwd=tempfile.gettempdir()) | 
				
			||||||
 | 
					    stream_file_path = os.path.join(tempfile.gettempdir(), 'ubloxRaw.stream') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not os.path.isfile(stream_file_path): | 
				
			||||||
 | 
					      print('Extract file failed') | 
				
			||||||
 | 
					      sys.exit(-3) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print('Run regression test - CC parser...') | 
				
			||||||
 | 
					    if args.valgrind: | 
				
			||||||
 | 
					      subprocess.check_call(["valgrind", "--leak-check=full", os.path.join(ubloxd_dir, 'ubloxd_test'), stream_file_path, cc_output_dir]) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      subprocess.check_call([os.path.join(ubloxd_dir, 'ubloxd_test'), stream_file_path, cc_output_dir]) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print('Running regression test - py parser...') | 
				
			||||||
 | 
					    parser_test(stream_file_path, py_output_dir) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print('Running regression test - compare result...') | 
				
			||||||
 | 
					    r = compare_results(cc_output_dir, py_output_dir) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print('All done!') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    subprocess.check_call(["rm", stream_file_path]) | 
				
			||||||
 | 
					    subprocess.check_call(["rm", '-rf', cc_output_dir]) | 
				
			||||||
 | 
					    subprocess.check_call(["rm", '-rf', py_output_dir]) | 
				
			||||||
 | 
					    sys.exit(r) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  except subprocess.CalledProcessError as e: | 
				
			||||||
 | 
					    print('CI test failed with {}'.format(e.returncode)) | 
				
			||||||
 | 
					    sys.exit(e.returncode) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  parser = argparse.ArgumentParser(description="Ubloxd CI test", | 
				
			||||||
 | 
					                                   formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parser.add_argument("stream_gz_file", nargs='?', default='ubloxRaw.tar.gz', | 
				
			||||||
 | 
					                      help="UbloxRaw data stream zip file") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parser.add_argument("output_dir", nargs='?', default='out', | 
				
			||||||
 | 
					                      help="Output events temp directory") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parser.add_argument("--valgrind", default=False, action='store_true', | 
				
			||||||
 | 
					                      help="Run in valgrind") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  args = parser.parse_args() | 
				
			||||||
 | 
					  main(args) | 
				
			||||||
@ -0,0 +1,137 @@ | 
				
			|||||||
 | 
					def GET_FIELD_U(w, nb, pos): | 
				
			||||||
 | 
					  return (((w) >> (pos)) & ((1 << (nb)) - 1)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def twos_complement(v, nb): | 
				
			||||||
 | 
					  sign = v >> (nb - 1) | 
				
			||||||
 | 
					  value = v | 
				
			||||||
 | 
					  if sign != 0: | 
				
			||||||
 | 
					    value = value - (1 << nb) | 
				
			||||||
 | 
					  return value | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def GET_FIELD_S(w, nb, pos): | 
				
			||||||
 | 
					  v = GET_FIELD_U(w, nb, pos) | 
				
			||||||
 | 
					  return twos_complement(v, nb) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def extract_uint8(v, b): | 
				
			||||||
 | 
					  return (v >> (8 * (3 - b))) & 255 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def extract_int8(v, b): | 
				
			||||||
 | 
					  value = extract_uint8(v, b) | 
				
			||||||
 | 
					  if value > 127: | 
				
			||||||
 | 
					    value -= 256 | 
				
			||||||
 | 
					  return value | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EphemerisData: | 
				
			||||||
 | 
					  '''container for parsing a AID_EPH message | 
				
			||||||
 | 
					    Thanks to Sylvain Munaut <tnt@246tNt.com> | 
				
			||||||
 | 
					    http://cgit.osmocom.org/cgit/osmocom-lcs/tree/gps.c | 
				
			||||||
 | 
					on of this parser | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See IS-GPS-200F.pdf Table 20-III for the field meanings, scaling factors and | 
				
			||||||
 | 
					    field widths | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self, svId, subframes): | 
				
			||||||
 | 
					    from math import pow | 
				
			||||||
 | 
					    self.svId = svId | 
				
			||||||
 | 
					    week_no = GET_FIELD_U(subframes[1][2+0], 10, 20) | 
				
			||||||
 | 
					    # code_on_l2 = GET_FIELD_U(subframes[1][0],  2, 12) | 
				
			||||||
 | 
					    # sv_ura     = GET_FIELD_U(subframes[1][0],  4,  8) | 
				
			||||||
 | 
					    # sv_health  = GET_FIELD_U(subframes[1][0],  6,  2) | 
				
			||||||
 | 
					    # l2_p_flag  = GET_FIELD_U(subframes[1][1],  1, 23) | 
				
			||||||
 | 
					    t_gd = GET_FIELD_S(subframes[1][2+4], 8, 6) | 
				
			||||||
 | 
					    iodc = (GET_FIELD_U(subframes[1][2+0], 2, 6) << 8) | GET_FIELD_U( | 
				
			||||||
 | 
					      subframes[1][2+5], 8, 22) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    t_oc = GET_FIELD_U(subframes[1][2+5], 16, 6) | 
				
			||||||
 | 
					    a_f2 = GET_FIELD_S(subframes[1][2+6], 8, 22) | 
				
			||||||
 | 
					    a_f1 = GET_FIELD_S(subframes[1][2+6], 16, 6) | 
				
			||||||
 | 
					    a_f0 = GET_FIELD_S(subframes[1][2+7], 22, 8) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c_rs = GET_FIELD_S(subframes[2][2+0], 16, 6) | 
				
			||||||
 | 
					    delta_n = GET_FIELD_S(subframes[2][2+1], 16, 14) | 
				
			||||||
 | 
					    m_0 = (GET_FIELD_S(subframes[2][2+1], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					      subframes[2][2+2], 24, 6) | 
				
			||||||
 | 
					    c_uc = GET_FIELD_S(subframes[2][2+3], 16, 14) | 
				
			||||||
 | 
					    e = (GET_FIELD_U(subframes[2][2+3], 8, 6) << 24) | GET_FIELD_U(subframes[2][2+4], 24, 6) | 
				
			||||||
 | 
					    c_us = GET_FIELD_S(subframes[2][2+5], 16, 14) | 
				
			||||||
 | 
					    a_powhalf = (GET_FIELD_U(subframes[2][2+5], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					      subframes[2][2+6], 24, 6) | 
				
			||||||
 | 
					    t_oe = GET_FIELD_U(subframes[2][2+7], 16, 14) | 
				
			||||||
 | 
					    # fit_flag   = GET_FIELD_U(subframes[2][7],  1,  7) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c_ic = GET_FIELD_S(subframes[3][2+0], 16, 14) | 
				
			||||||
 | 
					    omega_0 = (GET_FIELD_S(subframes[3][2+0], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					      subframes[3][2+1], 24, 6) | 
				
			||||||
 | 
					    c_is = GET_FIELD_S(subframes[3][2+2], 16, 14) | 
				
			||||||
 | 
					    i_0 = (GET_FIELD_S(subframes[3][2+2], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					      subframes[3][2+3], 24, 6) | 
				
			||||||
 | 
					    c_rc = GET_FIELD_S(subframes[3][2+4], 16, 14) | 
				
			||||||
 | 
					    w = (GET_FIELD_S(subframes[3][2+4], 8, 6) << 24) | GET_FIELD_U(subframes[3][5], 24, 6) | 
				
			||||||
 | 
					    omega_dot = GET_FIELD_S(subframes[3][2+6], 24, 6) | 
				
			||||||
 | 
					    idot = GET_FIELD_S(subframes[3][2+7], 14, 8) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._rsvd1 = GET_FIELD_U(subframes[1][2+1], 23, 6) | 
				
			||||||
 | 
					    self._rsvd2 = GET_FIELD_U(subframes[1][2+2], 24, 6) | 
				
			||||||
 | 
					    self._rsvd3 = GET_FIELD_U(subframes[1][2+3], 24, 6) | 
				
			||||||
 | 
					    self._rsvd4 = GET_FIELD_U(subframes[1][2+4], 16, 14) | 
				
			||||||
 | 
					    self.aodo = GET_FIELD_U(subframes[2][2+7], 5, 8) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Definition of Pi used in the GPS coordinate system | 
				
			||||||
 | 
					    gpsPi = 3.1415926535898 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # now form variables in radians, meters and seconds etc | 
				
			||||||
 | 
					    self.Tgd = t_gd * pow(2, -31) | 
				
			||||||
 | 
					    self.A = pow(a_powhalf * pow(2, -19), 2.0) | 
				
			||||||
 | 
					    self.cic = c_ic * pow(2, -29) | 
				
			||||||
 | 
					    self.cis = c_is * pow(2, -29) | 
				
			||||||
 | 
					    self.crc = c_rc * pow(2, -5) | 
				
			||||||
 | 
					    self.crs = c_rs * pow(2, -5) | 
				
			||||||
 | 
					    self.cuc = c_uc * pow(2, -29) | 
				
			||||||
 | 
					    self.cus = c_us * pow(2, -29) | 
				
			||||||
 | 
					    self.deltaN = delta_n * pow(2, -43) * gpsPi | 
				
			||||||
 | 
					    self.ecc = e * pow(2, -33) | 
				
			||||||
 | 
					    self.i0 = i_0 * pow(2, -31) * gpsPi | 
				
			||||||
 | 
					    self.idot = idot * pow(2, -43) * gpsPi | 
				
			||||||
 | 
					    self.M0 = m_0 * pow(2, -31) * gpsPi | 
				
			||||||
 | 
					    self.omega = w * pow(2, -31) * gpsPi | 
				
			||||||
 | 
					    self.omega_dot = omega_dot * pow(2, -43) * gpsPi | 
				
			||||||
 | 
					    self.omega0 = omega_0 * pow(2, -31) * gpsPi | 
				
			||||||
 | 
					    self.toe = t_oe * pow(2, 4) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # clock correction information | 
				
			||||||
 | 
					    self.toc = t_oc * pow(2, 4) | 
				
			||||||
 | 
					    self.gpsWeek = week_no | 
				
			||||||
 | 
					    self.af0 = a_f0 * pow(2, -31) | 
				
			||||||
 | 
					    self.af1 = a_f1 * pow(2, -43) | 
				
			||||||
 | 
					    self.af2 = a_f2 * pow(2, -55) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    iode1 = GET_FIELD_U(subframes[2][2+0], 8, 22) | 
				
			||||||
 | 
					    iode2 = GET_FIELD_U(subframes[3][2+7], 8, 22) | 
				
			||||||
 | 
					    self.valid = (iode1 == iode2) and (iode1 == (iodc & 0xff)) | 
				
			||||||
 | 
					    self.iode = iode1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if GET_FIELD_U(subframes[4][2+0], 6, 22) == 56 and \ | 
				
			||||||
 | 
					       GET_FIELD_U(subframes[4][2+0], 2, 28) == 1 and \ | 
				
			||||||
 | 
					       GET_FIELD_U(subframes[5][2+0], 2, 28) == 1: | 
				
			||||||
 | 
					      a0 = GET_FIELD_S(subframes[4][2], 8, 14) * pow(2, -30) | 
				
			||||||
 | 
					      a1 = GET_FIELD_S(subframes[4][2], 8, 6) * pow(2, -27) | 
				
			||||||
 | 
					      a2 = GET_FIELD_S(subframes[4][3], 8, 22) * pow(2, -24) | 
				
			||||||
 | 
					      a3 = GET_FIELD_S(subframes[4][3], 8, 14) * pow(2, -24) | 
				
			||||||
 | 
					      b0 = GET_FIELD_S(subframes[4][3], 8, 6) * pow(2, 11) | 
				
			||||||
 | 
					      b1 = GET_FIELD_S(subframes[4][4], 8, 22) * pow(2, 14) | 
				
			||||||
 | 
					      b2 = GET_FIELD_S(subframes[4][4], 8, 14) * pow(2, 16) | 
				
			||||||
 | 
					      b3 = GET_FIELD_S(subframes[4][4], 8, 6) * pow(2, 16) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.ionoAlpha = [a0, a1, a2, a3] | 
				
			||||||
 | 
					      self.ionoBeta = [b0, b1, b2, b3] | 
				
			||||||
 | 
					      self.ionoCoeffsValid = True | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.ionoAlpha = [] | 
				
			||||||
 | 
					      self.ionoBeta = [] | 
				
			||||||
 | 
					      self.ionoCoeffsValid = False | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,53 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import numpy as np | 
				
			||||||
 | 
					import unittest | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.car.honda.interface import CarInterface | 
				
			||||||
 | 
					from selfdrive.car.honda.values import CAR | 
				
			||||||
 | 
					from selfdrive.controls.lib.vehicle_model import VehicleModel | 
				
			||||||
 | 
					from selfdrive.locationd.liblocationd_py import liblocationd # pylint: disable=no-name-in-module, import-error | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestParamsLearner(unittest.TestCase): | 
				
			||||||
 | 
					  def setUp(self): | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.CP = CarInterface.get_params(CAR.CIVIC) | 
				
			||||||
 | 
					    bts = self.CP.to_bytes() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.params_learner = liblocationd.params_learner_init(len(bts), bts, 0.0, 1.0, self.CP.steerRatio, 1.0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def test_convergence(self): | 
				
			||||||
 | 
					    # Setup vehicle model with wrong parameters | 
				
			||||||
 | 
					    VM_sim = VehicleModel(self.CP) | 
				
			||||||
 | 
					    x_target = 0.75 | 
				
			||||||
 | 
					    sr_target = self.CP.steerRatio - 0.5 | 
				
			||||||
 | 
					    ao_target = -1.0 | 
				
			||||||
 | 
					    VM_sim.update_params(x_target, sr_target) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Run simulation | 
				
			||||||
 | 
					    times = np.arange(0, 15*3600, 0.01) | 
				
			||||||
 | 
					    angle_offset = np.radians(ao_target) | 
				
			||||||
 | 
					    steering_angles = np.radians(10 * np.sin(2 * np.pi * times / 100.)) + angle_offset | 
				
			||||||
 | 
					    speeds = 10 * np.sin(2 * np.pi * times / 1000.) + 25 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, t in enumerate(times): | 
				
			||||||
 | 
					      u = speeds[i] | 
				
			||||||
 | 
					      sa = steering_angles[i] | 
				
			||||||
 | 
					      psi = VM_sim.yaw_rate(sa - angle_offset, u) | 
				
			||||||
 | 
					      liblocationd.params_learner_update(self.params_learner, psi, u, sa) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Verify learned parameters | 
				
			||||||
 | 
					    sr = liblocationd.params_learner_get_sR(self.params_learner) | 
				
			||||||
 | 
					    ao_slow = np.degrees(liblocationd.params_learner_get_slow_ao(self.params_learner)) | 
				
			||||||
 | 
					    x = liblocationd.params_learner_get_x(self.params_learner) | 
				
			||||||
 | 
					    self.assertAlmostEqual(x_target, x, places=1) | 
				
			||||||
 | 
					    self.assertAlmostEqual(ao_target, ao_slow, places=1) | 
				
			||||||
 | 
					    self.assertAlmostEqual(sr_target, sr, places=1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  unittest.main() | 
				
			||||||
@ -0,0 +1,996 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					''' | 
				
			||||||
 | 
					UBlox binary protocol handling | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright Andrew Tridgell, October 2012 | 
				
			||||||
 | 
					Released under GNU GPL version 3 or later | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WARNING: This code has originally intended for | 
				
			||||||
 | 
					ublox version 7, it has been adapted to work | 
				
			||||||
 | 
					for ublox version 8, not all functions may work. | 
				
			||||||
 | 
					''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import struct | 
				
			||||||
 | 
					import time, os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# protocol constants | 
				
			||||||
 | 
					PREAMBLE1 = 0xb5 | 
				
			||||||
 | 
					PREAMBLE2 = 0x62 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# message classes | 
				
			||||||
 | 
					CLASS_NAV = 0x01 | 
				
			||||||
 | 
					CLASS_RXM = 0x02 | 
				
			||||||
 | 
					CLASS_INF = 0x04 | 
				
			||||||
 | 
					CLASS_ACK = 0x05 | 
				
			||||||
 | 
					CLASS_CFG = 0x06 | 
				
			||||||
 | 
					CLASS_MON = 0x0A | 
				
			||||||
 | 
					CLASS_AID = 0x0B | 
				
			||||||
 | 
					CLASS_TIM = 0x0D | 
				
			||||||
 | 
					CLASS_ESF = 0x10 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ACK messages | 
				
			||||||
 | 
					MSG_ACK_NACK = 0x00 | 
				
			||||||
 | 
					MSG_ACK_ACK = 0x01 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NAV messages | 
				
			||||||
 | 
					MSG_NAV_POSECEF = 0x1 | 
				
			||||||
 | 
					MSG_NAV_POSLLH = 0x2 | 
				
			||||||
 | 
					MSG_NAV_STATUS = 0x3 | 
				
			||||||
 | 
					MSG_NAV_DOP = 0x4 | 
				
			||||||
 | 
					MSG_NAV_SOL = 0x6 | 
				
			||||||
 | 
					MSG_NAV_PVT = 0x7 | 
				
			||||||
 | 
					MSG_NAV_POSUTM = 0x8 | 
				
			||||||
 | 
					MSG_NAV_VELNED = 0x12 | 
				
			||||||
 | 
					MSG_NAV_VELECEF = 0x11 | 
				
			||||||
 | 
					MSG_NAV_TIMEGPS = 0x20 | 
				
			||||||
 | 
					MSG_NAV_TIMEUTC = 0x21 | 
				
			||||||
 | 
					MSG_NAV_CLOCK = 0x22 | 
				
			||||||
 | 
					MSG_NAV_SVINFO = 0x30 | 
				
			||||||
 | 
					MSG_NAV_AOPSTATUS = 0x60 | 
				
			||||||
 | 
					MSG_NAV_DGPS = 0x31 | 
				
			||||||
 | 
					MSG_NAV_DOP = 0x04 | 
				
			||||||
 | 
					MSG_NAV_EKFSTATUS = 0x40 | 
				
			||||||
 | 
					MSG_NAV_SBAS = 0x32 | 
				
			||||||
 | 
					MSG_NAV_SOL = 0x06 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# RXM messages | 
				
			||||||
 | 
					MSG_RXM_RAW = 0x15 | 
				
			||||||
 | 
					MSG_RXM_SFRB = 0x11 | 
				
			||||||
 | 
					MSG_RXM_SFRBX = 0x13 | 
				
			||||||
 | 
					MSG_RXM_SVSI = 0x20 | 
				
			||||||
 | 
					MSG_RXM_EPH = 0x31 | 
				
			||||||
 | 
					MSG_RXM_ALM = 0x30 | 
				
			||||||
 | 
					MSG_RXM_PMREQ = 0x41 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# AID messages | 
				
			||||||
 | 
					MSG_AID_ALM = 0x30 | 
				
			||||||
 | 
					MSG_AID_EPH = 0x31 | 
				
			||||||
 | 
					MSG_AID_ALPSRV = 0x32 | 
				
			||||||
 | 
					MSG_AID_AOP = 0x33 | 
				
			||||||
 | 
					MSG_AID_DATA = 0x10 | 
				
			||||||
 | 
					MSG_AID_ALP = 0x50 | 
				
			||||||
 | 
					MSG_AID_DATA = 0x10 | 
				
			||||||
 | 
					MSG_AID_HUI = 0x02 | 
				
			||||||
 | 
					MSG_AID_INI = 0x01 | 
				
			||||||
 | 
					MSG_AID_REQ = 0x00 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CFG messages | 
				
			||||||
 | 
					MSG_CFG_PRT = 0x00 | 
				
			||||||
 | 
					MSG_CFG_ANT = 0x13 | 
				
			||||||
 | 
					MSG_CFG_DAT = 0x06 | 
				
			||||||
 | 
					MSG_CFG_EKF = 0x12 | 
				
			||||||
 | 
					MSG_CFG_ESFGWT = 0x29 | 
				
			||||||
 | 
					MSG_CFG_CFG = 0x09 | 
				
			||||||
 | 
					MSG_CFG_USB = 0x1b | 
				
			||||||
 | 
					MSG_CFG_RATE = 0x08 | 
				
			||||||
 | 
					MSG_CFG_SET_RATE = 0x01 | 
				
			||||||
 | 
					MSG_CFG_NAV5 = 0x24 | 
				
			||||||
 | 
					MSG_CFG_FXN = 0x0E | 
				
			||||||
 | 
					MSG_CFG_INF = 0x02 | 
				
			||||||
 | 
					MSG_CFG_ITFM = 0x39 | 
				
			||||||
 | 
					MSG_CFG_MSG = 0x01 | 
				
			||||||
 | 
					MSG_CFG_NAVX5 = 0x23 | 
				
			||||||
 | 
					MSG_CFG_NMEA = 0x17 | 
				
			||||||
 | 
					MSG_CFG_NVS = 0x22 | 
				
			||||||
 | 
					MSG_CFG_PM2 = 0x3B | 
				
			||||||
 | 
					MSG_CFG_PM = 0x32 | 
				
			||||||
 | 
					MSG_CFG_RINV = 0x34 | 
				
			||||||
 | 
					MSG_CFG_RST = 0x04 | 
				
			||||||
 | 
					MSG_CFG_RXM = 0x11 | 
				
			||||||
 | 
					MSG_CFG_SBAS = 0x16 | 
				
			||||||
 | 
					MSG_CFG_TMODE2 = 0x3D | 
				
			||||||
 | 
					MSG_CFG_TMODE = 0x1D | 
				
			||||||
 | 
					MSG_CFG_TPS = 0x31 | 
				
			||||||
 | 
					MSG_CFG_TP = 0x07 | 
				
			||||||
 | 
					MSG_CFG_GNSS = 0x3E | 
				
			||||||
 | 
					MSG_CFG_ODO = 0x1E | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ESF messages | 
				
			||||||
 | 
					MSG_ESF_MEAS = 0x02 | 
				
			||||||
 | 
					MSG_ESF_STATUS = 0x10 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# INF messages | 
				
			||||||
 | 
					MSG_INF_DEBUG = 0x04 | 
				
			||||||
 | 
					MSG_INF_ERROR = 0x00 | 
				
			||||||
 | 
					MSG_INF_NOTICE = 0x02 | 
				
			||||||
 | 
					MSG_INF_TEST = 0x03 | 
				
			||||||
 | 
					MSG_INF_WARNING = 0x01 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MON messages | 
				
			||||||
 | 
					MSG_MON_SCHD = 0x01 | 
				
			||||||
 | 
					MSG_MON_HW = 0x09 | 
				
			||||||
 | 
					MSG_MON_HW2 = 0x0B | 
				
			||||||
 | 
					MSG_MON_IO = 0x02 | 
				
			||||||
 | 
					MSG_MON_MSGPP = 0x06 | 
				
			||||||
 | 
					MSG_MON_RXBUF = 0x07 | 
				
			||||||
 | 
					MSG_MON_RXR = 0x21 | 
				
			||||||
 | 
					MSG_MON_TXBUF = 0x08 | 
				
			||||||
 | 
					MSG_MON_VER = 0x04 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TIM messages | 
				
			||||||
 | 
					MSG_TIM_TP = 0x01 | 
				
			||||||
 | 
					MSG_TIM_TM2 = 0x03 | 
				
			||||||
 | 
					MSG_TIM_SVIN = 0x04 | 
				
			||||||
 | 
					MSG_TIM_VRFY = 0x06 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# port IDs | 
				
			||||||
 | 
					PORT_DDC = 0 | 
				
			||||||
 | 
					PORT_SERIAL1 = 1 | 
				
			||||||
 | 
					PORT_SERIAL2 = 2 | 
				
			||||||
 | 
					PORT_USB = 3 | 
				
			||||||
 | 
					PORT_SPI = 4 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dynamic models | 
				
			||||||
 | 
					DYNAMIC_MODEL_PORTABLE = 0 | 
				
			||||||
 | 
					DYNAMIC_MODEL_STATIONARY = 2 | 
				
			||||||
 | 
					DYNAMIC_MODEL_PEDESTRIAN = 3 | 
				
			||||||
 | 
					DYNAMIC_MODEL_AUTOMOTIVE = 4 | 
				
			||||||
 | 
					DYNAMIC_MODEL_SEA = 5 | 
				
			||||||
 | 
					DYNAMIC_MODEL_AIRBORNE1G = 6 | 
				
			||||||
 | 
					DYNAMIC_MODEL_AIRBORNE2G = 7 | 
				
			||||||
 | 
					DYNAMIC_MODEL_AIRBORNE4G = 8 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#reset items | 
				
			||||||
 | 
					RESET_HOT = 0 | 
				
			||||||
 | 
					RESET_WARM = 1 | 
				
			||||||
 | 
					RESET_COLD = 0xFFFF | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RESET_HW = 0 | 
				
			||||||
 | 
					RESET_SW = 1 | 
				
			||||||
 | 
					RESET_SW_GPS = 2 | 
				
			||||||
 | 
					RESET_HW_GRACEFUL = 4 | 
				
			||||||
 | 
					RESET_GPS_STOP = 8 | 
				
			||||||
 | 
					RESET_GPS_START = 9 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UBloxError(Exception): | 
				
			||||||
 | 
					  '''Ublox error class''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self, msg): | 
				
			||||||
 | 
					    Exception.__init__(self, msg) | 
				
			||||||
 | 
					    self.message = msg | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UBloxAttrDict(dict): | 
				
			||||||
 | 
					  '''allow dictionary members as attributes''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self): | 
				
			||||||
 | 
					    dict.__init__(self) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __getattr__(self, name): | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      return self.__getitem__(name) | 
				
			||||||
 | 
					    except KeyError: | 
				
			||||||
 | 
					      raise AttributeError(name) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __setattr__(self, name, value): | 
				
			||||||
 | 
					    if name in self.__dict__: | 
				
			||||||
 | 
					      # allow set on normal attributes | 
				
			||||||
 | 
					      dict.__setattr__(self, name, value) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.__setitem__(name, value) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def ArrayParse(field): | 
				
			||||||
 | 
					  '''parse an array descriptor''' | 
				
			||||||
 | 
					  arridx = field.find('[') | 
				
			||||||
 | 
					  if arridx == -1: | 
				
			||||||
 | 
					    return (field, -1) | 
				
			||||||
 | 
					  alen = int(field[arridx + 1:-1]) | 
				
			||||||
 | 
					  fieldname = field[:arridx] | 
				
			||||||
 | 
					  return (fieldname, alen) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UBloxDescriptor: | 
				
			||||||
 | 
					  '''class used to describe the layout of a UBlox message''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self, | 
				
			||||||
 | 
					               name, | 
				
			||||||
 | 
					               msg_format, | 
				
			||||||
 | 
					               fields=None, | 
				
			||||||
 | 
					               count_field=None, | 
				
			||||||
 | 
					               format2=None, | 
				
			||||||
 | 
					               fields2=None): | 
				
			||||||
 | 
					    if fields is None: | 
				
			||||||
 | 
					      fields = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.name = name | 
				
			||||||
 | 
					    self.msg_format = msg_format | 
				
			||||||
 | 
					    self.fields = fields | 
				
			||||||
 | 
					    self.count_field = count_field | 
				
			||||||
 | 
					    self.format2 = format2 | 
				
			||||||
 | 
					    self.fields2 = fields2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def unpack(self, msg): | 
				
			||||||
 | 
					    '''unpack a UBloxMessage, creating the .fields and ._recs attributes in msg''' | 
				
			||||||
 | 
					    msg._fields = {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # unpack main message blocks. A comm | 
				
			||||||
 | 
					    formats = self.msg_format.split(',') | 
				
			||||||
 | 
					    buf = msg._buf[6:-2] | 
				
			||||||
 | 
					    count = 0 | 
				
			||||||
 | 
					    msg._recs = [] | 
				
			||||||
 | 
					    fields = self.fields[:] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for fmt in formats: | 
				
			||||||
 | 
					      size1 = struct.calcsize(fmt) | 
				
			||||||
 | 
					      if size1 > len(buf): | 
				
			||||||
 | 
					        raise UBloxError("%s INVALID_SIZE1=%u" % (self.name, len(buf))) | 
				
			||||||
 | 
					      f1 = list(struct.unpack(fmt, buf[:size1])) | 
				
			||||||
 | 
					      i = 0 | 
				
			||||||
 | 
					      while i < len(f1): | 
				
			||||||
 | 
					        field = fields.pop(0) | 
				
			||||||
 | 
					        (fieldname, alen) = ArrayParse(field) | 
				
			||||||
 | 
					        if alen == -1: | 
				
			||||||
 | 
					          msg._fields[fieldname] = f1[i] | 
				
			||||||
 | 
					          if self.count_field == fieldname: | 
				
			||||||
 | 
					            count = int(f1[i]) | 
				
			||||||
 | 
					          i += 1 | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					          msg._fields[fieldname] = [0] * alen | 
				
			||||||
 | 
					          for a in range(alen): | 
				
			||||||
 | 
					            msg._fields[fieldname][a] = f1[i] | 
				
			||||||
 | 
					            i += 1 | 
				
			||||||
 | 
					      buf = buf[size1:] | 
				
			||||||
 | 
					      if len(buf) == 0: | 
				
			||||||
 | 
					        break | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if self.count_field == '_remaining': | 
				
			||||||
 | 
					      count = len(buf) // struct.calcsize(self.format2) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if count == 0: | 
				
			||||||
 | 
					      msg._unpacked = True | 
				
			||||||
 | 
					      if len(buf) != 0: | 
				
			||||||
 | 
					        raise UBloxError("EXTRA_BYTES=%u" % len(buf)) | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size2 = struct.calcsize(self.format2) | 
				
			||||||
 | 
					    for c in range(count): | 
				
			||||||
 | 
					      r = UBloxAttrDict() | 
				
			||||||
 | 
					      if size2 > len(buf): | 
				
			||||||
 | 
					        raise UBloxError("INVALID_SIZE=%u, " % len(buf)) | 
				
			||||||
 | 
					      f2 = list(struct.unpack(self.format2, buf[:size2])) | 
				
			||||||
 | 
					      for i in range(len(self.fields2)): | 
				
			||||||
 | 
					        r[self.fields2[i]] = f2[i] | 
				
			||||||
 | 
					      buf = buf[size2:] | 
				
			||||||
 | 
					      msg._recs.append(r) | 
				
			||||||
 | 
					    if len(buf) != 0: | 
				
			||||||
 | 
					      raise UBloxError("EXTRA_BYTES=%u" % len(buf)) | 
				
			||||||
 | 
					    msg._unpacked = True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pack(self, msg, msg_class=None, msg_id=None): | 
				
			||||||
 | 
					    '''pack a UBloxMessage from the .fields and ._recs attributes in msg''' | 
				
			||||||
 | 
					    f1 = [] | 
				
			||||||
 | 
					    if msg_class is None: | 
				
			||||||
 | 
					      msg_class = msg.msg_class() | 
				
			||||||
 | 
					    if msg_id is None: | 
				
			||||||
 | 
					      msg_id = msg.msg_id() | 
				
			||||||
 | 
					    msg._buf = '' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fields = self.fields[:] | 
				
			||||||
 | 
					    for f in fields: | 
				
			||||||
 | 
					      (fieldname, alen) = ArrayParse(f) | 
				
			||||||
 | 
					      if not fieldname in msg._fields: | 
				
			||||||
 | 
					        break | 
				
			||||||
 | 
					      if alen == -1: | 
				
			||||||
 | 
					        f1.append(msg._fields[fieldname]) | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        for a in range(alen): | 
				
			||||||
 | 
					          f1.append(msg._fields[fieldname][a]) | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      # try full length message | 
				
			||||||
 | 
					      fmt = self.msg_format.replace(',', '') | 
				
			||||||
 | 
					      msg._buf = struct.pack(fmt, *tuple(f1)) | 
				
			||||||
 | 
					    except Exception: | 
				
			||||||
 | 
					      # try without optional part | 
				
			||||||
 | 
					      fmt = self.msg_format.split(',')[0] | 
				
			||||||
 | 
					      msg._buf = struct.pack(fmt, *tuple(f1)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    length = len(msg._buf) | 
				
			||||||
 | 
					    if msg._recs: | 
				
			||||||
 | 
					      length += len(msg._recs) * struct.calcsize(self.format2) | 
				
			||||||
 | 
					    header = struct.pack('<BBBBH', PREAMBLE1, PREAMBLE2, msg_class, msg_id, length) | 
				
			||||||
 | 
					    msg._buf = header + msg._buf | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for r in msg._recs: | 
				
			||||||
 | 
					      f2 = [] | 
				
			||||||
 | 
					      for f in self.fields2: | 
				
			||||||
 | 
					        f2.append(r[f]) | 
				
			||||||
 | 
					      msg._buf += struct.pack(self.format2, *tuple(f2)) | 
				
			||||||
 | 
					    msg._buf += struct.pack('<BB', *msg.checksum(data=msg._buf[2:])) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def format(self, msg): | 
				
			||||||
 | 
					    '''return a formatted string for a message''' | 
				
			||||||
 | 
					    if not msg._unpacked: | 
				
			||||||
 | 
					      self.unpack(msg) | 
				
			||||||
 | 
					    ret = self.name + ': ' | 
				
			||||||
 | 
					    for f in self.fields: | 
				
			||||||
 | 
					      (fieldname, alen) = ArrayParse(f) | 
				
			||||||
 | 
					      if not fieldname in msg._fields: | 
				
			||||||
 | 
					        continue | 
				
			||||||
 | 
					      v = msg._fields[fieldname] | 
				
			||||||
 | 
					      if isinstance(v, list): | 
				
			||||||
 | 
					        ret += '%s=[' % fieldname | 
				
			||||||
 | 
					        for a in range(alen): | 
				
			||||||
 | 
					          ret += '%s, ' % v[a] | 
				
			||||||
 | 
					        ret = ret[:-2] + '], ' | 
				
			||||||
 | 
					      elif isinstance(v, str): | 
				
			||||||
 | 
					        ret += '%s="%s", ' % (f, v.rstrip(' \0')) | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        ret += '%s=%s, ' % (f, v) | 
				
			||||||
 | 
					    for r in msg._recs: | 
				
			||||||
 | 
					      ret += '[ ' | 
				
			||||||
 | 
					      for f in self.fields2: | 
				
			||||||
 | 
					        v = r[f] | 
				
			||||||
 | 
					        ret += '%s=%s, ' % (f, v) | 
				
			||||||
 | 
					      ret = ret[:-2] + ' ], ' | 
				
			||||||
 | 
					    return ret[:-2] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# list of supported message types. | 
				
			||||||
 | 
					msg_types = { | 
				
			||||||
 | 
					  (CLASS_ACK, MSG_ACK_ACK): | 
				
			||||||
 | 
					  UBloxDescriptor('ACK_ACK', '<BB', ['clsID', 'msgID']), | 
				
			||||||
 | 
					  (CLASS_ACK, MSG_ACK_NACK): | 
				
			||||||
 | 
					  UBloxDescriptor('ACK_NACK', '<BB', ['clsID', 'msgID']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_USB): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_USB', '<HHHHHH32s32s32s', [ | 
				
			||||||
 | 
					    'vendorID', 'productID', 'reserved1', 'reserved2', 'powerConsumption', 'flags', | 
				
			||||||
 | 
					    'vendorString', 'productString', 'serialNumber' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_PRT): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_PRT', '<BBHIIHHHH', [ | 
				
			||||||
 | 
					    'portID', 'reserved0', 'txReady', 'mode', 'baudRate', 'inProtoMask', 'outProtoMask', | 
				
			||||||
 | 
					    'reserved4', 'reserved5' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_CFG): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_CFG', '<III,B', | 
				
			||||||
 | 
					                  ['clearMask', 'saveMask', 'loadMask', 'deviceMask']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_RXM): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_RXM', '<BB', | 
				
			||||||
 | 
					                  ['reserved1', 'lpMode']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_RST): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_RST', '<HBB', ['navBbrMask ', 'resetMode', 'reserved1']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_SBAS): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_SBAS', '<BBBBI', | 
				
			||||||
 | 
					                  ['mode', 'usage', 'maxSBAS', 'scanmode2', 'scanmode1']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_GNSS): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_GNSS', '<BBBB', | 
				
			||||||
 | 
					                  ['msgVer', 'numTrkChHw', 'numTrkChUse', | 
				
			||||||
 | 
					                   'numConfigBlocks'], 'numConfigBlocks', '<BBBBI', | 
				
			||||||
 | 
					                  ['gnssId', 'resTrkCh', 'maxTrkCh', 'reserved1', 'flags']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_RATE): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_RATE', '<HHH', ['measRate', 'navRate', 'timeRef']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_MSG): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_MSG', '<BB6B', ['msgClass', 'msgId', 'rates[6]']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_POSLLH): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_POSLLH', '<IiiiiII', | 
				
			||||||
 | 
					                  ['iTOW', 'Longitude', 'Latitude', 'height', 'hMSL', 'hAcc', 'vAcc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_VELNED): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_VELNED', '<IiiiIIiII', [ | 
				
			||||||
 | 
					    'iTOW', 'velN', 'velE', 'velD', 'speed', 'gSpeed', 'heading', 'sAcc', 'cAcc' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_DOP): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_DOP', '<IHHHHHHH', | 
				
			||||||
 | 
					                  ['iTOW', 'gDOP', 'pDOP', 'tDOP', 'vDOP', 'hDOP', 'nDOP', 'eDOP']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_STATUS): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_STATUS', '<IBBBBII', | 
				
			||||||
 | 
					                  ['iTOW', 'gpsFix', 'flags', 'fixStat', 'flags2', 'ttff', 'msss']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_SOL): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_SOL', '<IihBBiiiIiiiIHBBI', [ | 
				
			||||||
 | 
					    'iTOW', 'fTOW', 'week', 'gpsFix', 'flags', 'ecefX', 'ecefY', 'ecefZ', 'pAcc', | 
				
			||||||
 | 
					    'ecefVX', 'ecefVY', 'ecefVZ', 'sAcc', 'pDOP', 'reserved1', 'numSV', 'reserved2' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_PVT): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_PVT', '<IHBBBBBBIiBBBBiiiiIIiiiiiIIH6BihH', [ | 
				
			||||||
 | 
					    'iTOW', 'year', 'month', 'day', 'hour', 'min', 'sec', 'valid', 'tAcc', 'nano', | 
				
			||||||
 | 
					    'fixType', 'flags', 'flags2', 'numSV', 'lon', 'lat', 'height', 'hMSL', 'hAcc', 'vAcc', | 
				
			||||||
 | 
					    'velN', 'velE', 'velD', 'gSpeed', 'headMot', 'sAcc', 'headAcc', 'pDOP', | 
				
			||||||
 | 
					    'reserverd1[6]', 'headVeh', 'magDec', 'magAcc' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_POSUTM): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_POSUTM', '<Iiiibb', | 
				
			||||||
 | 
					                  ['iTOW', 'East', 'North', 'Alt', 'Zone', 'Hem']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_SBAS): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_SBAS', '<IBBbBBBBB', [ | 
				
			||||||
 | 
					    'iTOW', 'geo', 'mode', 'sys', 'service', 'cnt', 'reserved01', 'reserved02', | 
				
			||||||
 | 
					    'reserved03' | 
				
			||||||
 | 
					  ], 'cnt', 'BBBBBBhHh', [ | 
				
			||||||
 | 
					    'svid', 'flags', 'udre', 'svSys', 'svService', 'reserved1', 'prc', 'reserved2', 'ic' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_POSECEF): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_POSECEF', '<IiiiI', ['iTOW', 'ecefX', 'ecefY', 'ecefZ', 'pAcc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_VELECEF): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_VELECEF', '<IiiiI', ['iTOW', 'ecefVX', 'ecefVY', 'ecefVZ', | 
				
			||||||
 | 
					                                            'sAcc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_TIMEGPS): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_TIMEGPS', '<IihbBI', | 
				
			||||||
 | 
					                  ['iTOW', 'fTOW', 'week', 'leapS', 'valid', 'tAcc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_TIMEUTC): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_TIMEUTC', '<IIiHBBBBBB', [ | 
				
			||||||
 | 
					    'iTOW', 'tAcc', 'nano', 'year', 'month', 'day', 'hour', 'min', 'sec', 'valid' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_CLOCK): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_CLOCK', '<IiiII', ['iTOW', 'clkB', 'clkD', 'tAcc', 'fAcc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_DGPS): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_DGPS', '<IihhBBH', | 
				
			||||||
 | 
					                  ['iTOW', 'age', 'baseId', 'baseHealth', 'numCh', 'status', 'reserved1'], | 
				
			||||||
 | 
					                  'numCh', '<BBHff', ['svid', 'flags', 'ageC', 'prc', 'prrc']), | 
				
			||||||
 | 
					  (CLASS_NAV, MSG_NAV_SVINFO): | 
				
			||||||
 | 
					  UBloxDescriptor('NAV_SVINFO', '<IBBH', ['iTOW', 'numCh', 'globalFlags', | 
				
			||||||
 | 
					                                          'reserved2'], 'numCh', '<BBBBBbhi', | 
				
			||||||
 | 
					                  ['chn', 'svid', 'flags', 'quality', 'cno', 'elev', 'azim', 'prRes']), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_SVSI): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_SVSI', '<IhBB', ['iTOW', 'week', 'numVis', 'numSV'], 'numSV', | 
				
			||||||
 | 
					                  '<BBhbB', ['svid', 'svFlag', 'azim', 'elev', 'age']), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_EPH): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_EPH', '<II , 8I 8I 8I', | 
				
			||||||
 | 
					                  ['svid', 'how', 'sf1d[8]', 'sf2d[8]', 'sf3d[8]']), | 
				
			||||||
 | 
					  (CLASS_AID, MSG_AID_EPH): | 
				
			||||||
 | 
					  UBloxDescriptor('AID_EPH', '<II , 8I 8I 8I', | 
				
			||||||
 | 
					                  ['svid', 'how', 'sf1d[8]', 'sf2d[8]', 'sf3d[8]']), | 
				
			||||||
 | 
					  (CLASS_AID, MSG_AID_HUI): | 
				
			||||||
 | 
					  UBloxDescriptor('AID_HUI', '<Iddi 6h 8f I', | 
				
			||||||
 | 
					                  ['health', 'utcA0', 'utcA1', 'utcTOW', 'utcWNT', 'utcLS', 'utcWNF', | 
				
			||||||
 | 
					                   'utcDN', 'utcLSF', 'utcSpare', 'klobA0', 'klobA1', 'klobA2', 'klobA3', | 
				
			||||||
 | 
					                   'klobB0', 'klobB1', 'klobB2', 'klobB3', 'flags']), | 
				
			||||||
 | 
					  (CLASS_AID, MSG_AID_AOP): | 
				
			||||||
 | 
					  UBloxDescriptor('AID_AOP', '<B47B , 48B 48B 48B', | 
				
			||||||
 | 
					                  ['svid', 'data[47]', 'optional0[48]', 'optional1[48]', | 
				
			||||||
 | 
					                   'optional1[48]']), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_RAW): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_RAW', '<dHbBB3B', [ | 
				
			||||||
 | 
					    'rcvTow', 'week', 'leapS', 'numMeas', 'recStat', 'reserved1[3]' | 
				
			||||||
 | 
					  ], 'numMeas', '<ddfBBBBHBBBBBB', [ | 
				
			||||||
 | 
					    'prMes', 'cpMes', 'doMes', 'gnssId', 'svId', 'sigId', 'freqId', 'locktime', 'cno', | 
				
			||||||
 | 
					    'prStdev', 'cpStdev', 'doStdev', 'trkStat', 'reserved3' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_SFRB): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_SFRB', '<BB10I', ['chn', 'svid', 'dwrd[10]']), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_SFRBX): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_SFRBX', '<8B', ['gnssId', 'svid', 'reserved1', 'freqId', 'numWords', | 
				
			||||||
 | 
					      'reserved2', 'version', 'reserved3'], 'numWords', 'I', ['dwrd']), | 
				
			||||||
 | 
					  (CLASS_AID, MSG_AID_ALM): | 
				
			||||||
 | 
					  UBloxDescriptor('AID_ALM', '<II', '_remaining', 'I', ['dwrd']), | 
				
			||||||
 | 
					  (CLASS_RXM, MSG_RXM_ALM): | 
				
			||||||
 | 
					  UBloxDescriptor('RXM_ALM', '<II , 8I', ['svid', 'week', 'dwrd[8]']), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_ODO): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_ODO', '<B3BBB6BBB2BBB2B', [ | 
				
			||||||
 | 
					    'version', 'reserved1[3]', 'flags', 'odoCfg', 'reserverd2[6]', 'cogMaxSpeed', | 
				
			||||||
 | 
					    'cogMaxPosAcc', 'reserved3[2]', 'velLpGain', 'cogLpGain', 'reserved[2]' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_NAV5): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_NAV5', '<HBBiIbBHHHHBBIII', [ | 
				
			||||||
 | 
					    'mask', 'dynModel', 'fixMode', 'fixedAlt', 'fixedAltVar', 'minElev', 'drLimit', | 
				
			||||||
 | 
					    'pDop', 'tDop', 'pAcc', 'tAcc', 'staticHoldThresh', 'dgpsTimeOut', 'reserved2', | 
				
			||||||
 | 
					    'reserved3', 'reserved4' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_CFG, MSG_CFG_NAVX5): | 
				
			||||||
 | 
					  UBloxDescriptor('CFG_NAVX5', '<HHIBBBBBBBBBBHIBBBBBBHII', [ | 
				
			||||||
 | 
					    'version', 'mask1', 'reserved0', 'reserved1', 'reserved2', 'minSVs', 'maxSVs', | 
				
			||||||
 | 
					    'minCNO', 'reserved5', 'iniFix3D', 'reserved6', 'reserved7', 'reserved8', | 
				
			||||||
 | 
					    'wknRollover', 'reserved9', 'reserved10', 'reserved11', 'usePPP', 'useAOP', | 
				
			||||||
 | 
					    'reserved12', 'reserved13', 'aopOrbMaxErr', 'reserved3', 'reserved4' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_MON, MSG_MON_HW): | 
				
			||||||
 | 
					  UBloxDescriptor('MON_HW', '<IIIIHHBBBBIB25BHIII', [ | 
				
			||||||
 | 
					    'pinSel', 'pinBank', 'pinDir', 'pinVal', 'noisePerMS', 'agcCnt', 'aStatus', 'aPower', | 
				
			||||||
 | 
					    'flags', 'reserved1', 'usedMask', 'VP[25]', 'jamInd', 'reserved3', 'pinInq', 'pullH', | 
				
			||||||
 | 
					    'pullL' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_MON, MSG_MON_HW2): | 
				
			||||||
 | 
					  UBloxDescriptor('MON_HW2', '<bBbBB3BI8BI4B', [ | 
				
			||||||
 | 
					    'ofsI', 'magI', 'ofsQ', 'magQ', 'cfgSource', 'reserved1[3]', 'lowLevCfg', | 
				
			||||||
 | 
					    'reserved2[8]', 'postStatus', 'reserved3[4]' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_MON, MSG_MON_SCHD): | 
				
			||||||
 | 
					  UBloxDescriptor('MON_SCHD', '<IIIIHHHBB', [ | 
				
			||||||
 | 
					    'tskRun', 'tskSchd', 'tskOvrr', 'tskReg', 'stack', 'stackSize', 'CPUIdle', 'flySly', | 
				
			||||||
 | 
					    'ptlSly' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_MON, MSG_MON_VER): | 
				
			||||||
 | 
					  UBloxDescriptor('MON_VER', '<30s10s,30s', ['swVersion', 'hwVersion', 'romVersion'], | 
				
			||||||
 | 
					                  '_remaining', '30s', ['extension']), | 
				
			||||||
 | 
					  (CLASS_TIM, MSG_TIM_TP): | 
				
			||||||
 | 
					  UBloxDescriptor('TIM_TP', '<IIiHBB', | 
				
			||||||
 | 
					                  ['towMS', 'towSubMS', 'qErr', 'week', 'flags', 'reserved1']), | 
				
			||||||
 | 
					  (CLASS_TIM, MSG_TIM_TM2): | 
				
			||||||
 | 
					  UBloxDescriptor('TIM_TM2', '<BBHHHIIIII', [ | 
				
			||||||
 | 
					    'ch', 'flags', 'count', 'wnR', 'wnF', 'towMsR', 'towSubMsR', 'towMsF', 'towSubMsF', | 
				
			||||||
 | 
					    'accEst' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_TIM, MSG_TIM_SVIN): | 
				
			||||||
 | 
					  UBloxDescriptor('TIM_SVIN', '<IiiiIIBBH', [ | 
				
			||||||
 | 
					    'dur', 'meanX', 'meanY', 'meanZ', 'meanV', 'obs', 'valid', 'active', 'reserved1' | 
				
			||||||
 | 
					  ]), | 
				
			||||||
 | 
					  (CLASS_INF, MSG_INF_ERROR): | 
				
			||||||
 | 
					  UBloxDescriptor('INF_ERR', '<18s', ['str']), | 
				
			||||||
 | 
					  (CLASS_INF, MSG_INF_DEBUG): | 
				
			||||||
 | 
					  UBloxDescriptor('INF_DEBUG', '<18s', ['str']) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UBloxMessage: | 
				
			||||||
 | 
					  '''UBlox message class - holds a UBX binary message''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self): | 
				
			||||||
 | 
					    self._buf = b"" | 
				
			||||||
 | 
					    self._fields = {} | 
				
			||||||
 | 
					    self._recs = [] | 
				
			||||||
 | 
					    self._unpacked = False | 
				
			||||||
 | 
					    self.debug_level = 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __str__(self): | 
				
			||||||
 | 
					    '''format a message as a string''' | 
				
			||||||
 | 
					    if not self.valid(): | 
				
			||||||
 | 
					      return 'UBloxMessage(INVALID)' | 
				
			||||||
 | 
					    type = self.msg_type() | 
				
			||||||
 | 
					    if type in msg_types: | 
				
			||||||
 | 
					      return msg_types[type].format(self) | 
				
			||||||
 | 
					    return 'UBloxMessage(UNKNOWN %s, %u)' % (str(type), self.msg_length()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def as_dict(self): | 
				
			||||||
 | 
					    '''format a message as a string''' | 
				
			||||||
 | 
					    if not self.valid(): | 
				
			||||||
 | 
					      return 'UBloxMessage(INVALID)' | 
				
			||||||
 | 
					    type = self.msg_type() | 
				
			||||||
 | 
					    if type in msg_types: | 
				
			||||||
 | 
					      return msg_types[type].format(self) | 
				
			||||||
 | 
					    return 'UBloxMessage(UNKNOWN %s, %u)' % (str(type), self.msg_length()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __getattr__(self, name): | 
				
			||||||
 | 
					    '''allow access to message fields''' | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      return self._fields[name] | 
				
			||||||
 | 
					    except KeyError: | 
				
			||||||
 | 
					      if name == 'recs': | 
				
			||||||
 | 
					        return self._recs | 
				
			||||||
 | 
					      raise AttributeError(name) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __setattr__(self, name, value): | 
				
			||||||
 | 
					    '''allow access to message fields''' | 
				
			||||||
 | 
					    if name.startswith('_'): | 
				
			||||||
 | 
					      self.__dict__[name] = value | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self._fields[name] = value | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def have_field(self, name): | 
				
			||||||
 | 
					    '''return True if a message contains the given field''' | 
				
			||||||
 | 
					    return name in self._fields | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def debug(self, level, msg): | 
				
			||||||
 | 
					    '''write a debug message''' | 
				
			||||||
 | 
					    if self.debug_level >= level: | 
				
			||||||
 | 
					      print(msg) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def unpack(self): | 
				
			||||||
 | 
					    '''unpack a message''' | 
				
			||||||
 | 
					    if not self.valid(): | 
				
			||||||
 | 
					      raise UBloxError('INVALID MESSAGE') | 
				
			||||||
 | 
					    type = self.msg_type() | 
				
			||||||
 | 
					    if not type in msg_types: | 
				
			||||||
 | 
					      raise UBloxError('Unknown message %s length=%u' % (str(type), len(self._buf))) | 
				
			||||||
 | 
					    msg_types[type].unpack(self) | 
				
			||||||
 | 
					    return self._fields, self._recs | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def pack(self): | 
				
			||||||
 | 
					    '''pack a message''' | 
				
			||||||
 | 
					    if not self.valid(): | 
				
			||||||
 | 
					      raise UBloxError('INVALID MESSAGE') | 
				
			||||||
 | 
					    type = self.msg_type() | 
				
			||||||
 | 
					    if not type in msg_types: | 
				
			||||||
 | 
					      raise UBloxError('Unknown message %s' % str(type)) | 
				
			||||||
 | 
					    msg_types[type].pack(self) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def name(self): | 
				
			||||||
 | 
					    '''return the short string name for a message''' | 
				
			||||||
 | 
					    if not self.valid(): | 
				
			||||||
 | 
					      raise UBloxError('INVALID MESSAGE') | 
				
			||||||
 | 
					    type = self.msg_type() | 
				
			||||||
 | 
					    if not type in msg_types: | 
				
			||||||
 | 
					      raise UBloxError('Unknown message %s length=%u' % (str(type), len(self._buf))) | 
				
			||||||
 | 
					    return msg_types[type].name | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def msg_class(self): | 
				
			||||||
 | 
					    '''return the message class''' | 
				
			||||||
 | 
					    return self._buf[2] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def msg_id(self): | 
				
			||||||
 | 
					    '''return the message id within the class''' | 
				
			||||||
 | 
					    return self._buf[3] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def msg_type(self): | 
				
			||||||
 | 
					    '''return the message type tuple (class, id)''' | 
				
			||||||
 | 
					    return (self.msg_class(), self.msg_id()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def msg_length(self): | 
				
			||||||
 | 
					    '''return the payload length''' | 
				
			||||||
 | 
					    (payload_length, ) = struct.unpack('<H', self._buf[4:6]) | 
				
			||||||
 | 
					    return payload_length | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def valid_so_far(self): | 
				
			||||||
 | 
					    '''check if the message is valid so far''' | 
				
			||||||
 | 
					    if len(self._buf) > 0 and self._buf[0] != PREAMBLE1: | 
				
			||||||
 | 
					      return False | 
				
			||||||
 | 
					    if len(self._buf) > 1 and self._buf[1] != PREAMBLE2: | 
				
			||||||
 | 
					      self.debug(1, "bad pre2") | 
				
			||||||
 | 
					      return False | 
				
			||||||
 | 
					    if self.needed_bytes() == 0 and not self.valid(): | 
				
			||||||
 | 
					      if len(self._buf) > 8: | 
				
			||||||
 | 
					        self.debug(1, "bad checksum len=%u needed=%u" % (len(self._buf), | 
				
			||||||
 | 
					                                                         self.needed_bytes())) | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        self.debug(1, "bad len len=%u needed=%u" % (len(self._buf), self.needed_bytes())) | 
				
			||||||
 | 
					      return False | 
				
			||||||
 | 
					    return True | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def add(self, bytes): | 
				
			||||||
 | 
					    '''add some bytes to a message''' | 
				
			||||||
 | 
					    self._buf += bytes | 
				
			||||||
 | 
					    while not self.valid_so_far() and len(self._buf) > 0: | 
				
			||||||
 | 
					      '''handle corrupted streams''' | 
				
			||||||
 | 
					      self._buf = self._buf[1:] | 
				
			||||||
 | 
					    if self.needed_bytes() < 0: | 
				
			||||||
 | 
					      self._buf = "" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def checksum(self, data=None): | 
				
			||||||
 | 
					    '''return a checksum tuple for a message''' | 
				
			||||||
 | 
					    if data is None: | 
				
			||||||
 | 
					      data = self._buf[2:-2] | 
				
			||||||
 | 
					    #cs = 0 | 
				
			||||||
 | 
					    ck_a = 0 | 
				
			||||||
 | 
					    ck_b = 0 | 
				
			||||||
 | 
					    for i in data: | 
				
			||||||
 | 
					      ck_a = (ck_a + i) & 0xFF | 
				
			||||||
 | 
					      ck_b = (ck_b + ck_a) & 0xFF | 
				
			||||||
 | 
					    return (ck_a, ck_b) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def valid_checksum(self): | 
				
			||||||
 | 
					    '''check if the checksum is OK''' | 
				
			||||||
 | 
					    (ck_a, ck_b) = self.checksum() | 
				
			||||||
 | 
					    #d = self._buf[2:-2] | 
				
			||||||
 | 
					    (ck_a2, ck_b2) = struct.unpack('<BB', self._buf[-2:]) | 
				
			||||||
 | 
					    return ck_a == ck_a2 and ck_b == ck_b2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def needed_bytes(self): | 
				
			||||||
 | 
					    '''return number of bytes still needed''' | 
				
			||||||
 | 
					    if len(self._buf) < 6: | 
				
			||||||
 | 
					      return 8 - len(self._buf) | 
				
			||||||
 | 
					    return self.msg_length() + 8 - len(self._buf) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def valid(self): | 
				
			||||||
 | 
					    '''check if a message is valid''' | 
				
			||||||
 | 
					    return len(self._buf) >= 8 and self.needed_bytes() == 0 and self.valid_checksum() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UBlox: | 
				
			||||||
 | 
					  '''main UBlox control class. | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    port can be a file (for reading only) or a serial device | 
				
			||||||
 | 
					    ''' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def __init__(self, port, baudrate=115200, timeout=0, panda=False, grey=False): | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.serial_device = port | 
				
			||||||
 | 
					    self.baudrate = baudrate | 
				
			||||||
 | 
					    self.use_sendrecv = False | 
				
			||||||
 | 
					    self.read_only = False | 
				
			||||||
 | 
					    self.debug_level = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if panda: | 
				
			||||||
 | 
					      from panda import Panda, PandaSerial | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.panda = Panda() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # resetting U-Blox module | 
				
			||||||
 | 
					      self.panda.set_esp_power(0) | 
				
			||||||
 | 
					      time.sleep(0.1) | 
				
			||||||
 | 
					      self.panda.set_esp_power(1) | 
				
			||||||
 | 
					      time.sleep(0.5) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # can't set above 9600 now... | 
				
			||||||
 | 
					      self.baudrate = 9600 | 
				
			||||||
 | 
					      self.dev = PandaSerial(self.panda, 1, self.baudrate) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.baudrate = 460800 | 
				
			||||||
 | 
					      print("upping baud:",self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,1,0007,0003,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      time.sleep(0.1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.dev = PandaSerial(self.panda, 1, self.baudrate) | 
				
			||||||
 | 
					    elif grey: | 
				
			||||||
 | 
					      import cereal.messaging as messaging | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      class BoarddSerial(): | 
				
			||||||
 | 
					        def __init__(self): | 
				
			||||||
 | 
					          self.ubloxRaw = messaging.sub_sock('ubloxRaw') | 
				
			||||||
 | 
					          self.buf = "" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def read(self, n): | 
				
			||||||
 | 
					          for msg in messaging.drain_sock(self.ubloxRaw, len(self.buf) < n): | 
				
			||||||
 | 
					            self.buf += msg.ubloxRaw | 
				
			||||||
 | 
					          ret = self.buf[:n] | 
				
			||||||
 | 
					          self.buf = self.buf[n:] | 
				
			||||||
 | 
					          return ret | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def write(self, dat): | 
				
			||||||
 | 
					          pass | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      self.dev = BoarddSerial() | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      if self.serial_device.startswith("tcp:"): | 
				
			||||||
 | 
					        import socket | 
				
			||||||
 | 
					        a = self.serial_device.split(':') | 
				
			||||||
 | 
					        destination_addr = (a[1], int(a[2])) | 
				
			||||||
 | 
					        self.dev = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
				
			||||||
 | 
					        self.dev.connect(destination_addr) | 
				
			||||||
 | 
					        self.dev.setblocking(1) | 
				
			||||||
 | 
					        self.dev.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) | 
				
			||||||
 | 
					        self.use_sendrecv = True | 
				
			||||||
 | 
					      elif os.path.isfile(self.serial_device): | 
				
			||||||
 | 
					        self.read_only = True | 
				
			||||||
 | 
					        self.dev = open(self.serial_device, mode='rb') | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        import serial | 
				
			||||||
 | 
					        self.dev = serial.Serial( | 
				
			||||||
 | 
					          self.serial_device, | 
				
			||||||
 | 
					          baudrate=self.baudrate, | 
				
			||||||
 | 
					          dsrdtr=False, | 
				
			||||||
 | 
					          rtscts=False, | 
				
			||||||
 | 
					          xonxoff=False, | 
				
			||||||
 | 
					          timeout=timeout) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.logfile = None | 
				
			||||||
 | 
					    self.log = None | 
				
			||||||
 | 
					    self.preferred_dynamic_model = None | 
				
			||||||
 | 
					    self.preferred_usePPP = None | 
				
			||||||
 | 
					    self.preferred_dgps_timeout = None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def close(self): | 
				
			||||||
 | 
					    '''close the device''' | 
				
			||||||
 | 
					    self.dev.close() | 
				
			||||||
 | 
					    self.dev = None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_debug(self, debug_level): | 
				
			||||||
 | 
					    '''set debug level''' | 
				
			||||||
 | 
					    self.debug_level = debug_level | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def debug(self, level, msg): | 
				
			||||||
 | 
					    '''write a debug message''' | 
				
			||||||
 | 
					    if self.debug_level >= level: | 
				
			||||||
 | 
					      print(msg) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_logfile(self, logfile, append=False): | 
				
			||||||
 | 
					    '''setup logging to a file''' | 
				
			||||||
 | 
					    if self.log is not None: | 
				
			||||||
 | 
					      self.log.close() | 
				
			||||||
 | 
					      self.log = None | 
				
			||||||
 | 
					    self.logfile = logfile | 
				
			||||||
 | 
					    if self.logfile is not None: | 
				
			||||||
 | 
					      if append: | 
				
			||||||
 | 
					        mode = 'ab' | 
				
			||||||
 | 
					      else: | 
				
			||||||
 | 
					        mode = 'wb' | 
				
			||||||
 | 
					      self.log = open(self.logfile, mode=mode) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_preferred_dynamic_model(self, model): | 
				
			||||||
 | 
					    '''set the preferred dynamic model for receiver''' | 
				
			||||||
 | 
					    self.preferred_dynamic_model = model | 
				
			||||||
 | 
					    if model is not None: | 
				
			||||||
 | 
					      self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_preferred_dgps_timeout(self, timeout): | 
				
			||||||
 | 
					    '''set the preferred DGPS timeout for receiver''' | 
				
			||||||
 | 
					    self.preferred_dgps_timeout = timeout | 
				
			||||||
 | 
					    if timeout is not None: | 
				
			||||||
 | 
					      self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_preferred_usePPP(self, usePPP): | 
				
			||||||
 | 
					    '''set the preferred usePPP setting for the receiver''' | 
				
			||||||
 | 
					    if usePPP is None: | 
				
			||||||
 | 
					      self.preferred_usePPP = None | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    self.preferred_usePPP = int(usePPP) | 
				
			||||||
 | 
					    self.configure_poll(CLASS_CFG, MSG_CFG_NAVX5) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def nmea_checksum(self, msg): | 
				
			||||||
 | 
					    d = msg[1:] | 
				
			||||||
 | 
					    cs = 0 | 
				
			||||||
 | 
					    for i in d: | 
				
			||||||
 | 
					      cs ^= ord(i) | 
				
			||||||
 | 
					    return cs | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def write(self, buf): | 
				
			||||||
 | 
					    '''write some bytes''' | 
				
			||||||
 | 
					    if not self.read_only: | 
				
			||||||
 | 
					      if self.use_sendrecv: | 
				
			||||||
 | 
					        return self.dev.send(buf) | 
				
			||||||
 | 
					      return self.dev.write(buf) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def read(self, n): | 
				
			||||||
 | 
					    '''read some bytes''' | 
				
			||||||
 | 
					    if self.use_sendrecv: | 
				
			||||||
 | 
					      import socket | 
				
			||||||
 | 
					      try: | 
				
			||||||
 | 
					        return self.dev.recv(n) | 
				
			||||||
 | 
					      except socket.error: | 
				
			||||||
 | 
					        return '' | 
				
			||||||
 | 
					    return self.dev.read(n) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send_nmea(self, msg): | 
				
			||||||
 | 
					    if not self.read_only: | 
				
			||||||
 | 
					      s = msg + "*%02X" % self.nmea_checksum(msg) + "\r\n" | 
				
			||||||
 | 
					      self.write(s) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_binary(self): | 
				
			||||||
 | 
					    '''put a UBlox into binary mode using a NMEA string''' | 
				
			||||||
 | 
					    if not self.read_only: | 
				
			||||||
 | 
					      print("try set binary at %u" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,0,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,1,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,2,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,3,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,4,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					      self.send_nmea("$PUBX,41,5,0007,0001,%u,0" % self.baudrate) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def disable_nmea(self): | 
				
			||||||
 | 
					    ''' stop sending all types of nmea messages ''' | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,GSV,1,1,1,1,1,0") | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,GGA,0,0,0,0,0,0") | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,GSA,0,0,0,0,0,0") | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,VTG,0,0,0,0,0,0") | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,TXT,0,0,0,0,0,0") | 
				
			||||||
 | 
					    self.send_nmea("$PUBX,40,RMC,0,0,0,0,0,0") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def seek_percent(self, pct): | 
				
			||||||
 | 
					    '''seek to the given percentage of a file''' | 
				
			||||||
 | 
					    self.dev.seek(0, 2) | 
				
			||||||
 | 
					    filesize = self.dev.tell() | 
				
			||||||
 | 
					    self.dev.seek(pct * 0.01 * filesize) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def special_handling(self, msg): | 
				
			||||||
 | 
					    '''handle automatic configuration changes''' | 
				
			||||||
 | 
					    if msg.name() == 'CFG_NAV5': | 
				
			||||||
 | 
					      msg.unpack() | 
				
			||||||
 | 
					      sendit = False | 
				
			||||||
 | 
					      pollit = False | 
				
			||||||
 | 
					      if self.preferred_dynamic_model is not None and msg.dynModel != self.preferred_dynamic_model: | 
				
			||||||
 | 
					        msg.dynModel = self.preferred_dynamic_model | 
				
			||||||
 | 
					        sendit = True | 
				
			||||||
 | 
					        pollit = True | 
				
			||||||
 | 
					      if self.preferred_dgps_timeout is not None and msg.dgpsTimeOut != self.preferred_dgps_timeout: | 
				
			||||||
 | 
					        msg.dgpsTimeOut = self.preferred_dgps_timeout | 
				
			||||||
 | 
					        self.debug(2, "Setting dgpsTimeOut=%u" % msg.dgpsTimeOut) | 
				
			||||||
 | 
					        sendit = True | 
				
			||||||
 | 
					        # we don't re-poll for this one, as some receivers refuse to set it | 
				
			||||||
 | 
					      if sendit: | 
				
			||||||
 | 
					        msg.pack() | 
				
			||||||
 | 
					        self.send(msg) | 
				
			||||||
 | 
					        if pollit: | 
				
			||||||
 | 
					          self.configure_poll(CLASS_CFG, MSG_CFG_NAV5) | 
				
			||||||
 | 
					    if msg.name() == 'CFG_NAVX5' and self.preferred_usePPP is not None: | 
				
			||||||
 | 
					      msg.unpack() | 
				
			||||||
 | 
					      if msg.usePPP != self.preferred_usePPP: | 
				
			||||||
 | 
					        msg.usePPP = self.preferred_usePPP | 
				
			||||||
 | 
					        msg.mask = 1 << 13 | 
				
			||||||
 | 
					        msg.pack() | 
				
			||||||
 | 
					        self.send(msg) | 
				
			||||||
 | 
					        self.configure_poll(CLASS_CFG, MSG_CFG_NAVX5) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def receive_message(self, ignore_eof=False): | 
				
			||||||
 | 
					    '''blocking receive of one ublox message''' | 
				
			||||||
 | 
					    msg = UBloxMessage() | 
				
			||||||
 | 
					    while True: | 
				
			||||||
 | 
					      n = msg.needed_bytes() | 
				
			||||||
 | 
					      b = self.read(n) | 
				
			||||||
 | 
					      if not b: | 
				
			||||||
 | 
					        if ignore_eof: | 
				
			||||||
 | 
					          time.sleep(0.01) | 
				
			||||||
 | 
					          continue | 
				
			||||||
 | 
					        if len(msg._buf) > 0: | 
				
			||||||
 | 
					          self.debug(1, "dropping %d bytes" % len(msg._buf)) | 
				
			||||||
 | 
					        return None | 
				
			||||||
 | 
					      msg.add(b) | 
				
			||||||
 | 
					      if self.log is not None: | 
				
			||||||
 | 
					        self.log.write(b) | 
				
			||||||
 | 
					        self.log.flush() | 
				
			||||||
 | 
					      if msg.valid(): | 
				
			||||||
 | 
					        self.special_handling(msg) | 
				
			||||||
 | 
					        return msg | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def receive_message_noerror(self, ignore_eof=False): | 
				
			||||||
 | 
					    '''blocking receive of one ublox message, ignoring errors''' | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      return self.receive_message(ignore_eof=ignore_eof) | 
				
			||||||
 | 
					    except UBloxError as e: | 
				
			||||||
 | 
					      print(e) | 
				
			||||||
 | 
					      return None | 
				
			||||||
 | 
					    except OSError as e: | 
				
			||||||
 | 
					      # Occasionally we get hit with 'resource temporarily unavailable' | 
				
			||||||
 | 
					      # messages here on the serial device, catch them too. | 
				
			||||||
 | 
					      print(e) | 
				
			||||||
 | 
					      return None | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send(self, msg): | 
				
			||||||
 | 
					    '''send a preformatted ublox message''' | 
				
			||||||
 | 
					    if not msg.valid(): | 
				
			||||||
 | 
					      self.debug(1, "invalid send") | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    if not self.read_only: | 
				
			||||||
 | 
					      self.write(msg._buf) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send_message(self, msg_class, msg_id, payload): | 
				
			||||||
 | 
					    '''send a ublox message with class, id and payload''' | 
				
			||||||
 | 
					    msg = UBloxMessage() | 
				
			||||||
 | 
					    msg._buf = struct.pack('<BBBBH', 0xb5, 0x62, msg_class, msg_id, len(payload)) | 
				
			||||||
 | 
					    msg._buf += payload | 
				
			||||||
 | 
					    (ck_a, ck_b) = msg.checksum(msg._buf[2:]) | 
				
			||||||
 | 
					    msg._buf += struct.pack('<BB', ck_a, ck_b) | 
				
			||||||
 | 
					    self.send(msg) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_solution_rate(self, rate_ms=200, nav_rate=1, timeref=0): | 
				
			||||||
 | 
					    '''configure the solution rate in milliseconds''' | 
				
			||||||
 | 
					    payload = struct.pack('<HHH', rate_ms, nav_rate, timeref) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_RATE, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_message_rate(self, msg_class, msg_id, rate): | 
				
			||||||
 | 
					    '''configure the message rate for a given message''' | 
				
			||||||
 | 
					    payload = struct.pack('<BBB', msg_class, msg_id, rate) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_SET_RATE, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_port(self, port=1, inMask=3, outMask=3, mode=2240, baudrate=None): | 
				
			||||||
 | 
					    '''configure a IO port''' | 
				
			||||||
 | 
					    if baudrate is None: | 
				
			||||||
 | 
					      baudrate = self.baudrate | 
				
			||||||
 | 
					    payload = struct.pack('<BBH8BHHBBBB', port, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, inMask, | 
				
			||||||
 | 
					                          outMask, 0, 0, 0, 0) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_PRT, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_loadsave(self, clearMask=0, saveMask=0, loadMask=0, deviceMask=0): | 
				
			||||||
 | 
					    '''configure configuration load/save''' | 
				
			||||||
 | 
					    payload = struct.pack('<IIIB', clearMask, saveMask, loadMask, deviceMask) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_CFG, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_poll(self, msg_class, msg_id, payload=''): | 
				
			||||||
 | 
					    '''poll a configuration message''' | 
				
			||||||
 | 
					    self.send_message(msg_class, msg_id, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_poll_port(self, portID=None): | 
				
			||||||
 | 
					    '''poll a port configuration''' | 
				
			||||||
 | 
					    if portID is None: | 
				
			||||||
 | 
					      self.configure_poll(CLASS_CFG, MSG_CFG_PRT) | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      self.configure_poll(CLASS_CFG, MSG_CFG_PRT, struct.pack('<B', portID)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def configure_min_max_sats(self, min_sats=4, max_sats=32): | 
				
			||||||
 | 
					    '''Set the minimum/maximum number of satellites for a solution in the NAVX5 message''' | 
				
			||||||
 | 
					    payload = struct.pack('<HHIBBBBBBBBBBHIBBBBBBHII', 0, 4, 0, 0, 0, min_sats, max_sats, | 
				
			||||||
 | 
					                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_NAVX5, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def module_reset(self, set, mode): | 
				
			||||||
 | 
					    ''' Reset the module for hot/warm/cold start''' | 
				
			||||||
 | 
					    payload = struct.pack('<HBB', set, mode, 0) | 
				
			||||||
 | 
					    self.send_message(CLASS_CFG, MSG_CFG_RST, payload) | 
				
			||||||
@ -0,0 +1,3 @@ | 
				
			|||||||
 | 
					version https://git-lfs.github.com/spec/v1 | 
				
			||||||
 | 
					oid sha256:a15bc80894142f5b959fe83b627edf0c658ce881bac9447126b2979fdf5a2e1a | 
				
			||||||
 | 
					size 2069996 | 
				
			||||||
@ -0,0 +1,287 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import serial | 
				
			||||||
 | 
					from selfdrive.locationd.test import ublox | 
				
			||||||
 | 
					import time | 
				
			||||||
 | 
					import datetime | 
				
			||||||
 | 
					import struct | 
				
			||||||
 | 
					import sys | 
				
			||||||
 | 
					from cereal import log | 
				
			||||||
 | 
					from common import realtime | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					from selfdrive.locationd.test.ephemeris import EphemerisData, GET_FIELD_U | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					panda = os.getenv("PANDA") is not None   # panda directly connected | 
				
			||||||
 | 
					grey = not (os.getenv("EVAL") is not None)     # panda through boardd | 
				
			||||||
 | 
					debug = os.getenv("DEBUG") is not None   # debug prints | 
				
			||||||
 | 
					print_dB = os.getenv("PRINT_DB") is not None     # print antenna dB | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					timeout = 1 | 
				
			||||||
 | 
					dyn_model = 4 # auto model | 
				
			||||||
 | 
					baudrate = 460800 | 
				
			||||||
 | 
					ports = ["/dev/ttyACM0","/dev/ttyACM1"] | 
				
			||||||
 | 
					rate = 100 # send new data every 100ms | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# which SV IDs we have seen and when we got iono | 
				
			||||||
 | 
					svid_seen = {} | 
				
			||||||
 | 
					svid_ephemeris = {} | 
				
			||||||
 | 
					iono_seen = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def configure_ublox(dev): | 
				
			||||||
 | 
					  # configure ports  and solution parameters and rate | 
				
			||||||
 | 
					  # TODO: configure constellations and channels to allow for 10Hz and high precision | 
				
			||||||
 | 
					  dev.configure_port(port=ublox.PORT_USB, inMask=1, outMask=1) # enable only UBX on USB | 
				
			||||||
 | 
					  dev.configure_port(port=0, inMask=0, outMask=0) # disable DDC | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if panda: | 
				
			||||||
 | 
					    payload = struct.pack('<BBHIIHHHBB', 1, 0, 0, 2240, baudrate, 1, 1, 0, 0, 0) | 
				
			||||||
 | 
					    dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_PRT, payload) # enable UART | 
				
			||||||
 | 
					  else: | 
				
			||||||
 | 
					    payload = struct.pack('<BBHIIHHHBB', 1, 0, 0, 2240, baudrate, 0, 0, 0, 0, 0) | 
				
			||||||
 | 
					    dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_PRT, payload) # disable UART | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dev.configure_port(port=4, inMask=0, outMask=0) # disable SPI | 
				
			||||||
 | 
					  dev.configure_poll_port() | 
				
			||||||
 | 
					  dev.configure_poll_port(ublox.PORT_SERIAL1) | 
				
			||||||
 | 
					  dev.configure_poll_port(ublox.PORT_SERIAL2) | 
				
			||||||
 | 
					  dev.configure_poll_port(ublox.PORT_USB) | 
				
			||||||
 | 
					  dev.configure_solution_rate(rate_ms=rate) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Configure solution | 
				
			||||||
 | 
					  payload = struct.pack('<HBBIIBB4H6BH6B', 5, 4, 3, 0, 0, | 
				
			||||||
 | 
					                                           0, 0, 0, 0, 0, | 
				
			||||||
 | 
					                                           0, 0, 0, 0, 0, | 
				
			||||||
 | 
					                                           0, 0, 0, 0, 0, | 
				
			||||||
 | 
					                                           0, 0, 0, 0) | 
				
			||||||
 | 
					  dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_NAV5, payload) | 
				
			||||||
 | 
					  payload = struct.pack('<B3BBB6BBB2BBB2B', 0, 0, 0, 0, 1, | 
				
			||||||
 | 
					                                            3, 0, 0, 0, 0, | 
				
			||||||
 | 
					                                            0, 0, 0, 0, 0, | 
				
			||||||
 | 
					                                            0, 0, 0, 0, 0) | 
				
			||||||
 | 
					  dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_ODO, payload) | 
				
			||||||
 | 
					  #payload = struct.pack('<HHIBBBBBBBBBBH6BBB2BH4B3BB', 0, 8192, 0, 0, 0, | 
				
			||||||
 | 
					  #                                                     0, 0, 0, 0, 0, 0, | 
				
			||||||
 | 
					  #                                                     0, 0, 0, 0, 0, 0, | 
				
			||||||
 | 
					  #                                                     0, 0, 0, 0, 0, 0, | 
				
			||||||
 | 
					  #                                                     0, 0, 0, 0, 0, 0, | 
				
			||||||
 | 
					  #                                                     0, 0, 0, 0) | 
				
			||||||
 | 
					  #dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_NAVX5, payload) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_NAV5) | 
				
			||||||
 | 
					  dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_NAVX5) | 
				
			||||||
 | 
					  dev.configure_poll(ublox.CLASS_CFG, ublox.MSG_CFG_ODO) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Configure RAW and PVT messages to be sent every solution cycle | 
				
			||||||
 | 
					  dev.configure_message_rate(ublox.CLASS_NAV, ublox.MSG_NAV_PVT, 1) | 
				
			||||||
 | 
					  dev.configure_message_rate(ublox.CLASS_RXM, ublox.MSG_RXM_RAW, 1) | 
				
			||||||
 | 
					  dev.configure_message_rate(ublox.CLASS_RXM, ublox.MSG_RXM_SFRBX, 1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def int_to_bool_list(num): | 
				
			||||||
 | 
					  # for parsing bool bytes | 
				
			||||||
 | 
					  return [bool(num & (1<<n)) for n in range(8)] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_ephemeris(ephem_data): | 
				
			||||||
 | 
					  ephem = {'ephemeris': | 
				
			||||||
 | 
					          {'svId': ephem_data.svId, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'toc': ephem_data.toc, | 
				
			||||||
 | 
					           'gpsWeek': ephem_data.gpsWeek, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'af0': ephem_data.af0, | 
				
			||||||
 | 
					           'af1': ephem_data.af1, | 
				
			||||||
 | 
					           'af2': ephem_data.af2, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'iode': ephem_data.iode, | 
				
			||||||
 | 
					           'crs': ephem_data.crs, | 
				
			||||||
 | 
					           'deltaN': ephem_data.deltaN, | 
				
			||||||
 | 
					           'm0': ephem_data.M0, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'cuc': ephem_data.cuc, | 
				
			||||||
 | 
					           'ecc': ephem_data.ecc, | 
				
			||||||
 | 
					           'cus': ephem_data.cus, | 
				
			||||||
 | 
					           'a': ephem_data.A, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'toe': ephem_data.toe, | 
				
			||||||
 | 
					           'cic': ephem_data.cic, | 
				
			||||||
 | 
					           'omega0': ephem_data.omega0, | 
				
			||||||
 | 
					           'cis': ephem_data.cis, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'i0': ephem_data.i0, | 
				
			||||||
 | 
					           'crc': ephem_data.crc, | 
				
			||||||
 | 
					           'omega': ephem_data.omega, | 
				
			||||||
 | 
					           'omegaDot': ephem_data.omega_dot, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'iDot': ephem_data.idot, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'tgd': ephem_data.Tgd, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           'ionoCoeffsValid': ephem_data.ionoCoeffsValid, | 
				
			||||||
 | 
					           'ionoAlpha': ephem_data.ionoAlpha, | 
				
			||||||
 | 
					           'ionoBeta': ephem_data.ionoBeta}} | 
				
			||||||
 | 
					  return log.Event.new_message(ubloxGnss=ephem) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_solution(msg): | 
				
			||||||
 | 
					  msg_data = msg.unpack()[0] # Solutions do not have any data in repeated blocks | 
				
			||||||
 | 
					  timestamp = int(((datetime.datetime(msg_data['year'], | 
				
			||||||
 | 
					                                      msg_data['month'], | 
				
			||||||
 | 
					                                      msg_data['day'], | 
				
			||||||
 | 
					                                      msg_data['hour'], | 
				
			||||||
 | 
					                                      msg_data['min'], | 
				
			||||||
 | 
					                                      msg_data['sec']) | 
				
			||||||
 | 
					                 - datetime.datetime(1970,1,1)).total_seconds())*1e+03 | 
				
			||||||
 | 
					                 + msg_data['nano']*1e-06) | 
				
			||||||
 | 
					  gps_fix = {'bearing': msg_data['headMot']*1e-05,  # heading of motion in degrees | 
				
			||||||
 | 
					             'altitude': msg_data['height']*1e-03,  # altitude above ellipsoid | 
				
			||||||
 | 
					             'latitude': msg_data['lat']*1e-07,  # latitude in degrees | 
				
			||||||
 | 
					             'longitude': msg_data['lon']*1e-07,  # longitude in degrees | 
				
			||||||
 | 
					             'speed': msg_data['gSpeed']*1e-03,  # ground speed in meters | 
				
			||||||
 | 
					             'accuracy': msg_data['hAcc']*1e-03,  # horizontal accuracy (1 sigma?) | 
				
			||||||
 | 
					             'timestamp': timestamp,  # UTC time in ms since start of UTC stime | 
				
			||||||
 | 
					             'vNED': [msg_data['velN']*1e-03, | 
				
			||||||
 | 
					                     msg_data['velE']*1e-03, | 
				
			||||||
 | 
					                     msg_data['velD']*1e-03],  # velocity in NED frame in m/s | 
				
			||||||
 | 
					             'speedAccuracy': msg_data['sAcc']*1e-03,  # speed accuracy in m/s | 
				
			||||||
 | 
					             'verticalAccuracy': msg_data['vAcc']*1e-03,  # vertical accuracy in meters | 
				
			||||||
 | 
					             'bearingAccuracy': msg_data['headAcc']*1e-05,  # heading accuracy in degrees | 
				
			||||||
 | 
					             'source': 'ublox', | 
				
			||||||
 | 
					             'flags': msg_data['flags'], | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return log.Event.new_message(gpsLocationExternal=gps_fix) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_nav_data(msg, nav_frame_buffer): | 
				
			||||||
 | 
					  # TODO this stuff needs to be parsed and published. | 
				
			||||||
 | 
					  # refer to https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29.pdf | 
				
			||||||
 | 
					  # section 9.1 | 
				
			||||||
 | 
					  msg_meta_data, measurements = msg.unpack() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # parse GPS ephem | 
				
			||||||
 | 
					  gnssId = msg_meta_data['gnssId'] | 
				
			||||||
 | 
					  if gnssId  == 0: | 
				
			||||||
 | 
					    svId =  msg_meta_data['svid'] | 
				
			||||||
 | 
					    subframeId =  GET_FIELD_U(measurements[1]['dwrd'], 3, 8) | 
				
			||||||
 | 
					    words = [] | 
				
			||||||
 | 
					    for m in measurements: | 
				
			||||||
 | 
					      words.append(m['dwrd']) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # parse from | 
				
			||||||
 | 
					    if subframeId == 1: | 
				
			||||||
 | 
					      nav_frame_buffer[gnssId][svId] = {} | 
				
			||||||
 | 
					      nav_frame_buffer[gnssId][svId][subframeId] = words | 
				
			||||||
 | 
					    elif subframeId-1 in nav_frame_buffer[gnssId][svId]: | 
				
			||||||
 | 
					      nav_frame_buffer[gnssId][svId][subframeId] = words | 
				
			||||||
 | 
					    if len(nav_frame_buffer[gnssId][svId]) == 5: | 
				
			||||||
 | 
					      ephem_data = EphemerisData(svId, nav_frame_buffer[gnssId][svId]) | 
				
			||||||
 | 
					      return gen_ephemeris(ephem_data) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gen_raw(msg): | 
				
			||||||
 | 
					  # meta data is in first part of tuple | 
				
			||||||
 | 
					  # list of measurements is in second part | 
				
			||||||
 | 
					  msg_meta_data, measurements = msg.unpack() | 
				
			||||||
 | 
					  measurements_parsed = [] | 
				
			||||||
 | 
					  for m in measurements: | 
				
			||||||
 | 
					    trackingStatus_bools = int_to_bool_list(m['trkStat']) | 
				
			||||||
 | 
					    trackingStatus = {'pseudorangeValid': trackingStatus_bools[0], | 
				
			||||||
 | 
					                      'carrierPhaseValid': trackingStatus_bools[1], | 
				
			||||||
 | 
					                      'halfCycleValid': trackingStatus_bools[2], | 
				
			||||||
 | 
					                      'halfCycleSubtracted': trackingStatus_bools[3]} | 
				
			||||||
 | 
					    measurements_parsed.append({ | 
				
			||||||
 | 
					        'svId': m['svId'], | 
				
			||||||
 | 
					        'sigId': m['sigId'], | 
				
			||||||
 | 
					        'pseudorange': m['prMes'], | 
				
			||||||
 | 
					        'carrierCycles': m['cpMes'], | 
				
			||||||
 | 
					        'doppler': m['doMes'], | 
				
			||||||
 | 
					        'gnssId': m['gnssId'], | 
				
			||||||
 | 
					        'glonassFrequencyIndex': m['freqId'], | 
				
			||||||
 | 
					        'locktime': m['locktime'], | 
				
			||||||
 | 
					        'cno': m['cno'], | 
				
			||||||
 | 
					        'pseudorangeStdev': 0.01*(2**(m['prStdev'] & 15)), # weird scaling, might be wrong | 
				
			||||||
 | 
					        'carrierPhaseStdev': 0.004*(m['cpStdev'] & 15), | 
				
			||||||
 | 
					        'dopplerStdev': 0.002*(2**(m['doStdev'] & 15)), # weird scaling, might be wrong | 
				
			||||||
 | 
					        'trackingStatus': trackingStatus}) | 
				
			||||||
 | 
					  if print_dB: | 
				
			||||||
 | 
					    cnos = {} | 
				
			||||||
 | 
					    for meas in measurements_parsed: | 
				
			||||||
 | 
					      cnos[meas['svId']] = meas['cno'] | 
				
			||||||
 | 
					    print('Carrier to noise ratio for each sat: \n', cnos, '\n') | 
				
			||||||
 | 
					  receiverStatus_bools = int_to_bool_list(msg_meta_data['recStat']) | 
				
			||||||
 | 
					  receiverStatus = {'leapSecValid': receiverStatus_bools[0], | 
				
			||||||
 | 
					                    'clkReset': receiverStatus_bools[2]} | 
				
			||||||
 | 
					  raw_meas = {'measurementReport': {'rcvTow': msg_meta_data['rcvTow'], | 
				
			||||||
 | 
					                'gpsWeek': msg_meta_data['week'], | 
				
			||||||
 | 
					                'leapSeconds': msg_meta_data['leapS'], | 
				
			||||||
 | 
					                'receiverStatus': receiverStatus, | 
				
			||||||
 | 
					                'numMeas': msg_meta_data['numMeas'], | 
				
			||||||
 | 
					                'measurements': measurements_parsed}} | 
				
			||||||
 | 
					  return log.Event.new_message(ubloxGnss=raw_meas) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init_reader(): | 
				
			||||||
 | 
					  port_counter = 0 | 
				
			||||||
 | 
					  while True: | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      dev = ublox.UBlox(ports[port_counter], baudrate=baudrate, timeout=timeout, panda=panda, grey=grey) | 
				
			||||||
 | 
					      configure_ublox(dev) | 
				
			||||||
 | 
					      return dev | 
				
			||||||
 | 
					    except serial.serialutil.SerialException as e: | 
				
			||||||
 | 
					      print(e) | 
				
			||||||
 | 
					      port_counter = (port_counter + 1)%len(ports) | 
				
			||||||
 | 
					      time.sleep(2) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def handle_msg(dev, msg, nav_frame_buffer): | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    if debug: | 
				
			||||||
 | 
					      print(str(msg)) | 
				
			||||||
 | 
					      sys.stdout.flush() | 
				
			||||||
 | 
					    if msg.name() == 'NAV_PVT': | 
				
			||||||
 | 
					      sol = gen_solution(msg) | 
				
			||||||
 | 
					      sol.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					      gpsLocationExternal.send(sol.to_bytes()) | 
				
			||||||
 | 
					    elif msg.name() == 'RXM_RAW': | 
				
			||||||
 | 
					      raw = gen_raw(msg) | 
				
			||||||
 | 
					      raw.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					      ubloxGnss.send(raw.to_bytes()) | 
				
			||||||
 | 
					    elif msg.name() == 'RXM_SFRBX': | 
				
			||||||
 | 
					      nav = gen_nav_data(msg, nav_frame_buffer) | 
				
			||||||
 | 
					      if nav is not None: | 
				
			||||||
 | 
					        nav.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					        ubloxGnss.send(nav.to_bytes()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else: | 
				
			||||||
 | 
					      print("UNKNNOWN MESSAGE:", msg.name()) | 
				
			||||||
 | 
					  except ublox.UBloxError as e: | 
				
			||||||
 | 
					    print(e) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #if dev is not None and dev.dev is not None: | 
				
			||||||
 | 
					  #  dev.close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(gctx=None): | 
				
			||||||
 | 
					  global gpsLocationExternal, ubloxGnss | 
				
			||||||
 | 
					  nav_frame_buffer = {} | 
				
			||||||
 | 
					  nav_frame_buffer[0] = {} | 
				
			||||||
 | 
					  for i in range(1,33): | 
				
			||||||
 | 
					    nav_frame_buffer[0][i] = {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gpsLocationExternal = messaging.pub_sock('gpsLocationExternal') | 
				
			||||||
 | 
					  ubloxGnss = messaging.pub_sock('ubloxGnss') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dev = init_reader() | 
				
			||||||
 | 
					  while True: | 
				
			||||||
 | 
					    try: | 
				
			||||||
 | 
					      msg = dev.receive_message() | 
				
			||||||
 | 
					    except serial.serialutil.SerialException as e: | 
				
			||||||
 | 
					      print(e) | 
				
			||||||
 | 
					      dev.close() | 
				
			||||||
 | 
					      dev = init_reader() | 
				
			||||||
 | 
					    if msg is not None: | 
				
			||||||
 | 
					      handle_msg(dev, msg, nav_frame_buffer) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
@ -0,0 +1,53 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					from selfdrive.locationd.test import ublox | 
				
			||||||
 | 
					from common import realtime | 
				
			||||||
 | 
					from selfdrive.locationd.test.ubloxd import gen_raw, gen_solution | 
				
			||||||
 | 
					import zmq | 
				
			||||||
 | 
					import cereal.messaging as messaging | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unlogger = os.getenv("UNLOGGER") is not None   # debug prints | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(gctx=None): | 
				
			||||||
 | 
					  poller = zmq.Poller() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gpsLocationExternal = messaging.pub_sock('gpsLocationExternal') | 
				
			||||||
 | 
					  ubloxGnss = messaging.pub_sock('ubloxGnss') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # ubloxRaw = messaging.sub_sock('ubloxRaw', poller) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # buffer with all the messages that still need to be input into the kalman | 
				
			||||||
 | 
					  while 1: | 
				
			||||||
 | 
					    polld = poller.poll(timeout=1000) | 
				
			||||||
 | 
					    for sock, mode in polld: | 
				
			||||||
 | 
					      if mode != zmq.POLLIN: | 
				
			||||||
 | 
					        continue | 
				
			||||||
 | 
					      logs = messaging.drain_sock(sock) | 
				
			||||||
 | 
					      for log in logs: | 
				
			||||||
 | 
					        buff = log.ubloxRaw | 
				
			||||||
 | 
					        time = log.logMonoTime | 
				
			||||||
 | 
					        msg = ublox.UBloxMessage() | 
				
			||||||
 | 
					        msg.add(buff) | 
				
			||||||
 | 
					        if msg.valid(): | 
				
			||||||
 | 
					          if msg.name() == 'NAV_PVT': | 
				
			||||||
 | 
					            sol = gen_solution(msg) | 
				
			||||||
 | 
					            if unlogger: | 
				
			||||||
 | 
					              sol.logMonoTime = time | 
				
			||||||
 | 
					            else: | 
				
			||||||
 | 
					              sol.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					            gpsLocationExternal.send(sol.to_bytes()) | 
				
			||||||
 | 
					          elif msg.name() == 'RXM_RAW': | 
				
			||||||
 | 
					            raw = gen_raw(msg) | 
				
			||||||
 | 
					            if unlogger: | 
				
			||||||
 | 
					              raw.logMonoTime = time | 
				
			||||||
 | 
					            else: | 
				
			||||||
 | 
					              raw.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					            ubloxGnss.send(raw.to_bytes()) | 
				
			||||||
 | 
					        else: | 
				
			||||||
 | 
					          print("INVALID MESSAGE") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  main() | 
				
			||||||
@ -0,0 +1,77 @@ | 
				
			|||||||
 | 
					import sys | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from selfdrive.locationd.test.ublox import UBloxMessage | 
				
			||||||
 | 
					from selfdrive.locationd.test.ubloxd import gen_solution, gen_raw, gen_nav_data | 
				
			||||||
 | 
					from common import realtime | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mkdirs_exists_ok(path): | 
				
			||||||
 | 
					  try: | 
				
			||||||
 | 
					    os.makedirs(path) | 
				
			||||||
 | 
					  except OSError: | 
				
			||||||
 | 
					    if not os.path.isdir(path): | 
				
			||||||
 | 
					      raise | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parser_test(fn, prefix): | 
				
			||||||
 | 
					  nav_frame_buffer = {} | 
				
			||||||
 | 
					  nav_frame_buffer[0] = {} | 
				
			||||||
 | 
					  for i in range(1, 33): | 
				
			||||||
 | 
					    nav_frame_buffer[0][i] = {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if not os.path.exists(prefix): | 
				
			||||||
 | 
					    print('Prefix invalid') | 
				
			||||||
 | 
					    sys.exit(-1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  with open(fn, 'rb') as f: | 
				
			||||||
 | 
					    i = 0 | 
				
			||||||
 | 
					    saved_i = 0 | 
				
			||||||
 | 
					    msg = UBloxMessage() | 
				
			||||||
 | 
					    while True: | 
				
			||||||
 | 
					      n = msg.needed_bytes() | 
				
			||||||
 | 
					      b = f.read(n) | 
				
			||||||
 | 
					      if not b: | 
				
			||||||
 | 
					        break | 
				
			||||||
 | 
					      msg.add(b) | 
				
			||||||
 | 
					      if msg.valid(): | 
				
			||||||
 | 
					        i += 1 | 
				
			||||||
 | 
					        if msg.name() == 'NAV_PVT': | 
				
			||||||
 | 
					          sol = gen_solution(msg) | 
				
			||||||
 | 
					          sol.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					          with open(os.path.join(prefix, str(saved_i)), 'wb') as f1: | 
				
			||||||
 | 
					            f1.write(sol.to_bytes()) | 
				
			||||||
 | 
					            saved_i += 1 | 
				
			||||||
 | 
					        elif msg.name() == 'RXM_RAW': | 
				
			||||||
 | 
					          raw = gen_raw(msg) | 
				
			||||||
 | 
					          raw.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					          with open(os.path.join(prefix, str(saved_i)), 'wb') as f1: | 
				
			||||||
 | 
					            f1.write(raw.to_bytes()) | 
				
			||||||
 | 
					            saved_i += 1 | 
				
			||||||
 | 
					        elif msg.name() == 'RXM_SFRBX': | 
				
			||||||
 | 
					          nav = gen_nav_data(msg, nav_frame_buffer) | 
				
			||||||
 | 
					          if nav is not None: | 
				
			||||||
 | 
					            nav.logMonoTime = int(realtime.sec_since_boot() * 1e9) | 
				
			||||||
 | 
					            with open(os.path.join(prefix, str(saved_i)), 'wb') as f1: | 
				
			||||||
 | 
					              f1.write(nav.to_bytes()) | 
				
			||||||
 | 
					              saved_i += 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msg = UBloxMessage() | 
				
			||||||
 | 
					        msg.debug_level = 0 | 
				
			||||||
 | 
					    print('Parsed {} msgs'.format(i)) | 
				
			||||||
 | 
					    print('Generated {} cereal events'.format(saved_i)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  if len(sys.argv) < 3: | 
				
			||||||
 | 
					    print('Format: ubloxd_py_test.py file_path prefix') | 
				
			||||||
 | 
					    sys.exit(0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fn = sys.argv[1] | 
				
			||||||
 | 
					  if not os.path.isfile(fn): | 
				
			||||||
 | 
					    print('File path invalid') | 
				
			||||||
 | 
					    sys.exit(0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prefix = sys.argv[2] | 
				
			||||||
 | 
					  mkdirs_exists_ok(prefix) | 
				
			||||||
 | 
					  parser_test(fn, prefix) | 
				
			||||||
@ -0,0 +1,96 @@ | 
				
			|||||||
 | 
					#!/usr/bin/env python3 | 
				
			||||||
 | 
					import os | 
				
			||||||
 | 
					import sys | 
				
			||||||
 | 
					import argparse | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cereal import log | 
				
			||||||
 | 
					from common.basedir import BASEDIR | 
				
			||||||
 | 
					os.environ['BASEDIR'] = BASEDIR | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_arg_parser(): | 
				
			||||||
 | 
					  parser = argparse.ArgumentParser( | 
				
			||||||
 | 
					      description="Compare two result files", | 
				
			||||||
 | 
					      formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parser.add_argument("dir1", nargs='?', default='/data/ubloxdc', | 
				
			||||||
 | 
					                      help="Directory path 1 from which events are loaded") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parser.add_argument("dir2", nargs='?', default='/data/ubloxdpy', | 
				
			||||||
 | 
					                      help="Directory path 2 from which msgs are loaded") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return parser | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_file(fn): | 
				
			||||||
 | 
					  with open(fn, 'rb') as f: | 
				
			||||||
 | 
					    return f.read() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def compare_results(dir1, dir2): | 
				
			||||||
 | 
					  onlyfiles1 = [f for f in os.listdir(dir1) if os.path.isfile(os.path.join(dir1, f))] | 
				
			||||||
 | 
					  onlyfiles1.sort() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onlyfiles2 = [f for f in os.listdir(dir2) if os.path.isfile(os.path.join(dir2, f))] | 
				
			||||||
 | 
					  onlyfiles2.sort() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(onlyfiles1) != len(onlyfiles2): | 
				
			||||||
 | 
					    print('len mismatch: {} != {}'.format(len(onlyfiles1), len(onlyfiles2))) | 
				
			||||||
 | 
					    return -1 | 
				
			||||||
 | 
					  events1 = [log.Event.from_bytes(read_file(os.path.join(dir1, f))) for f in onlyfiles1] | 
				
			||||||
 | 
					  events2 = [log.Event.from_bytes(read_file(os.path.join(dir2, f))) for f in onlyfiles2] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for i in range(len(events1)): | 
				
			||||||
 | 
					    if events1[i].which() != events2[i].which(): | 
				
			||||||
 | 
					      print('event {} type mismatch: {} != {}'.format(i, events1[i].which(), events2[i].which())) | 
				
			||||||
 | 
					      return -2 | 
				
			||||||
 | 
					    if events1[i].which() == 'gpsLocationExternal': | 
				
			||||||
 | 
					      old_gps = events1[i].gpsLocationExternal | 
				
			||||||
 | 
					      gps = events2[i].gpsLocationExternal | 
				
			||||||
 | 
					      # print(gps, old_gps) | 
				
			||||||
 | 
					      attrs = ['flags', 'latitude', 'longitude', 'altitude', 'speed', 'bearing', | 
				
			||||||
 | 
					               'accuracy', 'timestamp', 'source', 'vNED', 'verticalAccuracy', 'bearingAccuracy', 'speedAccuracy'] | 
				
			||||||
 | 
					      for attr in attrs: | 
				
			||||||
 | 
					        o = getattr(old_gps, attr) | 
				
			||||||
 | 
					        n = getattr(gps, attr) | 
				
			||||||
 | 
					        if attr == 'vNED': | 
				
			||||||
 | 
					          if len(o) != len(n): | 
				
			||||||
 | 
					            print('Gps vNED len mismatch', o, n) | 
				
			||||||
 | 
					            return -3 | 
				
			||||||
 | 
					          else: | 
				
			||||||
 | 
					            for i in range(len(o)): | 
				
			||||||
 | 
					              if abs(o[i] - n[i]) > 1e-3: | 
				
			||||||
 | 
					                print('Gps vNED mismatch', o, n) | 
				
			||||||
 | 
					                return | 
				
			||||||
 | 
					        elif o != n: | 
				
			||||||
 | 
					          print('Gps mismatch', attr, o, n) | 
				
			||||||
 | 
					          return -4 | 
				
			||||||
 | 
					    elif events1[i].which() == 'ubloxGnss': | 
				
			||||||
 | 
					      old_gnss = events1[i].ubloxGnss | 
				
			||||||
 | 
					      gnss = events2[i].ubloxGnss | 
				
			||||||
 | 
					      if old_gnss.which() == 'measurementReport' and gnss.which() == 'measurementReport': | 
				
			||||||
 | 
					        attrs = ['gpsWeek', 'leapSeconds', 'measurements', 'numMeas', 'rcvTow', 'receiverStatus', 'schema'] | 
				
			||||||
 | 
					        for attr in attrs: | 
				
			||||||
 | 
					          o = getattr(old_gnss.measurementReport, attr) | 
				
			||||||
 | 
					          n = getattr(gnss.measurementReport, attr) | 
				
			||||||
 | 
					          if str(o) != str(n): | 
				
			||||||
 | 
					            print('measurementReport {} mismatched'.format(attr)) | 
				
			||||||
 | 
					            return -5 | 
				
			||||||
 | 
					        if not (str(old_gnss.measurementReport) == str(gnss.measurementReport)): | 
				
			||||||
 | 
					          print('Gnss measurementReport mismatched!') | 
				
			||||||
 | 
					          print('gnss measurementReport old', old_gnss.measurementReport.measurements) | 
				
			||||||
 | 
					          print('gnss measurementReport new', gnss.measurementReport.measurements) | 
				
			||||||
 | 
					          return -6 | 
				
			||||||
 | 
					      elif old_gnss.which() == 'ephemeris' and gnss.which() == 'ephemeris': | 
				
			||||||
 | 
					        if not (str(old_gnss.ephemeris) == str(gnss.ephemeris)): | 
				
			||||||
 | 
					          print('Gnss ephemeris mismatched!') | 
				
			||||||
 | 
					          print('gnss ephemeris old', old_gnss.ephemeris) | 
				
			||||||
 | 
					          print('gnss ephemeris new', gnss.ephemeris) | 
				
			||||||
 | 
					          return -7 | 
				
			||||||
 | 
					  print('All {} events matched!'.format(len(events1))) | 
				
			||||||
 | 
					  return 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__": | 
				
			||||||
 | 
					  args = get_arg_parser().parse_args(sys.argv[1:]) | 
				
			||||||
 | 
					  compare_results(args.dir1, args.dir2) | 
				
			||||||
@ -0,0 +1,374 @@ | 
				
			|||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include <stdlib.h> | 
				
			||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include <signal.h> | 
				
			||||||
 | 
					#include <unistd.h> | 
				
			||||||
 | 
					#include <sched.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <sys/cdefs.h> | 
				
			||||||
 | 
					#include <sys/types.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <assert.h> | 
				
			||||||
 | 
					#include <math.h> | 
				
			||||||
 | 
					#include <ctime> | 
				
			||||||
 | 
					#include <chrono> | 
				
			||||||
 | 
					#include <map> | 
				
			||||||
 | 
					#include <vector> | 
				
			||||||
 | 
					#include <algorithm> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <capnp/serialize.h> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/params.h" | 
				
			||||||
 | 
					#include "common/swaglog.h" | 
				
			||||||
 | 
					#include "common/timing.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ublox_msg.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define UBLOX_MSG_SIZE(hdr) (*(uint16_t *)&hdr[4]) | 
				
			||||||
 | 
					#define GET_FIELD_U(w, nb, pos) (((w) >> (pos)) & ((1<<(nb))-1)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ublox { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline int twos_complement(uint32_t v, uint32_t nb) { | 
				
			||||||
 | 
					  int sign = v >> (nb - 1); | 
				
			||||||
 | 
					  int value = v; | 
				
			||||||
 | 
					  if(sign != 0) | 
				
			||||||
 | 
					    value = value - (1 << nb); | 
				
			||||||
 | 
					  return value; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline int GET_FIELD_S(uint32_t w, uint32_t nb, uint32_t pos) { | 
				
			||||||
 | 
					  int v = GET_FIELD_U(w, nb, pos); | 
				
			||||||
 | 
					  return twos_complement(v, nb); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EphemerisData { | 
				
			||||||
 | 
					  public: | 
				
			||||||
 | 
					    EphemerisData(uint8_t svId, subframes_map subframes) { | 
				
			||||||
 | 
					      this->svId = svId; | 
				
			||||||
 | 
					      int week_no = GET_FIELD_U(subframes[1][2+0], 10, 20); | 
				
			||||||
 | 
					      int t_gd = GET_FIELD_S(subframes[1][2+4], 8, 6); | 
				
			||||||
 | 
					      int iodc = (GET_FIELD_U(subframes[1][2+0], 2, 6) << 8) | GET_FIELD_U( | 
				
			||||||
 | 
					        subframes[1][2+5], 8, 22); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int t_oc = GET_FIELD_U(subframes[1][2+5], 16, 6); | 
				
			||||||
 | 
					      int a_f2 = GET_FIELD_S(subframes[1][2+6], 8, 22); | 
				
			||||||
 | 
					      int a_f1 = GET_FIELD_S(subframes[1][2+6], 16, 6); | 
				
			||||||
 | 
					      int a_f0 = GET_FIELD_S(subframes[1][2+7], 22, 8); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int c_rs = GET_FIELD_S(subframes[2][2+0], 16, 6); | 
				
			||||||
 | 
					      int delta_n = GET_FIELD_S(subframes[2][2+1], 16, 14); | 
				
			||||||
 | 
					      int m_0 = (GET_FIELD_S(subframes[2][2+1], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					        subframes[2][2+2], 24, 6); | 
				
			||||||
 | 
					      int c_uc = GET_FIELD_S(subframes[2][2+3], 16, 14); | 
				
			||||||
 | 
					      int e = (GET_FIELD_U(subframes[2][2+3], 8, 6) << 24) | GET_FIELD_U(subframes[2][2+4], 24, 6); | 
				
			||||||
 | 
					      int c_us = GET_FIELD_S(subframes[2][2+5], 16, 14); | 
				
			||||||
 | 
					      uint32_t a_powhalf = (GET_FIELD_U(subframes[2][2+5], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					        subframes[2][2+6], 24, 6); | 
				
			||||||
 | 
					      int t_oe = GET_FIELD_U(subframes[2][2+7], 16, 14); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int c_ic = GET_FIELD_S(subframes[3][2+0], 16, 14); | 
				
			||||||
 | 
					      int omega_0 = (GET_FIELD_S(subframes[3][2+0], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					        subframes[3][2+1], 24, 6); | 
				
			||||||
 | 
					      int c_is = GET_FIELD_S(subframes[3][2+2], 16, 14); | 
				
			||||||
 | 
					      int i_0 = (GET_FIELD_S(subframes[3][2+2], 8, 6) << 24) | GET_FIELD_U( | 
				
			||||||
 | 
					        subframes[3][2+3], 24, 6); | 
				
			||||||
 | 
					      int c_rc = GET_FIELD_S(subframes[3][2+4], 16, 14); | 
				
			||||||
 | 
					      int w = (GET_FIELD_S(subframes[3][2+4], 8, 6) << 24) | GET_FIELD_U(subframes[3][5], 24, 6); | 
				
			||||||
 | 
					      int omega_dot = GET_FIELD_S(subframes[3][2+6], 24, 6); | 
				
			||||||
 | 
					      int idot = GET_FIELD_S(subframes[3][2+7], 14, 8); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->_rsvd1 = GET_FIELD_U(subframes[1][2+1], 23, 6); | 
				
			||||||
 | 
					      this->_rsvd2 = GET_FIELD_U(subframes[1][2+2], 24, 6); | 
				
			||||||
 | 
					      this->_rsvd3 = GET_FIELD_U(subframes[1][2+3], 24, 6); | 
				
			||||||
 | 
					      this->_rsvd4 = GET_FIELD_U(subframes[1][2+4], 16, 14); | 
				
			||||||
 | 
					      this->aodo = GET_FIELD_U(subframes[2][2+7], 5, 8); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      double gpsPi = 3.1415926535898; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // now form variables in radians, meters and seconds etc
 | 
				
			||||||
 | 
					      this->Tgd = t_gd * pow(2, -31); | 
				
			||||||
 | 
					      this->A = pow(a_powhalf * pow(2, -19), 2.0); | 
				
			||||||
 | 
					      this->cic = c_ic * pow(2, -29); | 
				
			||||||
 | 
					      this->cis = c_is * pow(2, -29); | 
				
			||||||
 | 
					      this->crc = c_rc * pow(2, -5); | 
				
			||||||
 | 
					      this->crs = c_rs * pow(2, -5); | 
				
			||||||
 | 
					      this->cuc = c_uc * pow(2, -29); | 
				
			||||||
 | 
					      this->cus = c_us * pow(2, -29); | 
				
			||||||
 | 
					      this->deltaN = delta_n * pow(2, -43) * gpsPi; | 
				
			||||||
 | 
					      this->ecc = e * pow(2, -33); | 
				
			||||||
 | 
					      this->i0 = i_0 * pow(2, -31) * gpsPi; | 
				
			||||||
 | 
					      this->idot = idot * pow(2, -43) * gpsPi; | 
				
			||||||
 | 
					      this->M0 = m_0 * pow(2, -31) * gpsPi; | 
				
			||||||
 | 
					      this->omega = w * pow(2, -31) * gpsPi; | 
				
			||||||
 | 
					      this->omega_dot = omega_dot * pow(2, -43) * gpsPi; | 
				
			||||||
 | 
					      this->omega0 = omega_0 * pow(2, -31) * gpsPi; | 
				
			||||||
 | 
					      this->toe = t_oe * pow(2, 4); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->toc = t_oc * pow(2, 4); | 
				
			||||||
 | 
					      this->gpsWeek = week_no; | 
				
			||||||
 | 
					      this->af0 = a_f0 * pow(2, -31); | 
				
			||||||
 | 
					      this->af1 = a_f1 * pow(2, -43); | 
				
			||||||
 | 
					      this->af2 = a_f2 * pow(2, -55); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint32_t iode1 = GET_FIELD_U(subframes[2][2+0], 8, 22); | 
				
			||||||
 | 
					      uint32_t iode2 = GET_FIELD_U(subframes[3][2+7], 8, 22); | 
				
			||||||
 | 
					      this->valid = (iode1 == iode2) && (iode1 == (iodc & 0xff)); | 
				
			||||||
 | 
					      this->iode = iode1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (GET_FIELD_U(subframes[4][2+0], 6, 22) == 56 && | 
				
			||||||
 | 
					        GET_FIELD_U(subframes[4][2+0], 2, 28) == 1 && | 
				
			||||||
 | 
					        GET_FIELD_U(subframes[5][2+0], 2, 28) == 1) { | 
				
			||||||
 | 
					        double a0 = GET_FIELD_S(subframes[4][2], 8, 14) * pow(2, -30); | 
				
			||||||
 | 
					        double a1 = GET_FIELD_S(subframes[4][2], 8, 6) * pow(2, -27); | 
				
			||||||
 | 
					        double a2 = GET_FIELD_S(subframes[4][3], 8, 22) * pow(2, -24); | 
				
			||||||
 | 
					        double a3 = GET_FIELD_S(subframes[4][3], 8, 14) * pow(2, -24); | 
				
			||||||
 | 
					        double b0 = GET_FIELD_S(subframes[4][3], 8, 6) * pow(2, 11); | 
				
			||||||
 | 
					        double b1 = GET_FIELD_S(subframes[4][4], 8, 22) * pow(2, 14); | 
				
			||||||
 | 
					        double b2 = GET_FIELD_S(subframes[4][4], 8, 14) * pow(2, 16); | 
				
			||||||
 | 
					        double b3 = GET_FIELD_S(subframes[4][4], 8, 6) * pow(2, 16); | 
				
			||||||
 | 
					        this->ionoAlpha[0] = a0;this->ionoAlpha[1] = a1;this->ionoAlpha[2] = a2;this->ionoAlpha[3] = a3; | 
				
			||||||
 | 
					        this->ionoBeta[0] = b0;this->ionoBeta[1] = b1;this->ionoBeta[2] = b2;this->ionoBeta[3] = b3; | 
				
			||||||
 | 
					        this->ionoCoeffsValid = true; | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        this->ionoCoeffsValid = false; | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    uint16_t svId; | 
				
			||||||
 | 
					    double Tgd, A, cic, cis, crc, crs, cuc, cus, deltaN, ecc, i0, idot, M0, omega, omega_dot, omega0, toe, toc; | 
				
			||||||
 | 
					    uint32_t gpsWeek, iode, _rsvd1, _rsvd2, _rsvd3, _rsvd4, aodo; | 
				
			||||||
 | 
					    double af0, af1, af2; | 
				
			||||||
 | 
					    bool valid; | 
				
			||||||
 | 
					    double ionoAlpha[4], ionoBeta[4]; | 
				
			||||||
 | 
					    bool ionoCoeffsValid; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UbloxMsgParser::UbloxMsgParser() :bytes_in_parse_buf(0) { | 
				
			||||||
 | 
					  nav_frame_buffer[0U] = std::map<uint8_t, subframes_map>(); | 
				
			||||||
 | 
					  for(int i = 1;i < 33;i++) | 
				
			||||||
 | 
					    nav_frame_buffer[0U][i] = subframes_map(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline int UbloxMsgParser::needed_bytes() { | 
				
			||||||
 | 
					  // Msg header incomplete?
 | 
				
			||||||
 | 
					  if(bytes_in_parse_buf < UBLOX_HEADER_SIZE) | 
				
			||||||
 | 
					    return UBLOX_HEADER_SIZE + UBLOX_CHECKSUM_SIZE - bytes_in_parse_buf; | 
				
			||||||
 | 
					  uint16_t needed = UBLOX_MSG_SIZE(msg_parse_buf) + UBLOX_HEADER_SIZE + UBLOX_CHECKSUM_SIZE; | 
				
			||||||
 | 
					  // too much data
 | 
				
			||||||
 | 
					  if(needed < (uint16_t)bytes_in_parse_buf) | 
				
			||||||
 | 
					    return -1; | 
				
			||||||
 | 
					  return needed - (uint16_t)bytes_in_parse_buf; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline bool UbloxMsgParser::valid_cheksum() { | 
				
			||||||
 | 
					  uint8_t ck_a = 0, ck_b = 0; | 
				
			||||||
 | 
					  for(int i = 2; i < bytes_in_parse_buf - UBLOX_CHECKSUM_SIZE;i++) { | 
				
			||||||
 | 
					    ck_a = (ck_a + msg_parse_buf[i]) & 0xFF; | 
				
			||||||
 | 
					    ck_b = (ck_b + ck_a) & 0xFF; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  if(ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { | 
				
			||||||
 | 
					    LOGD("Checksum a mismtach: %02X, %02X", ck_a, msg_parse_buf[6]); | 
				
			||||||
 | 
					    return false; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  if(ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { | 
				
			||||||
 | 
					    LOGD("Checksum b mismtach: %02X, %02X", ck_b, msg_parse_buf[7]); | 
				
			||||||
 | 
					    return false; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline bool UbloxMsgParser::valid() { | 
				
			||||||
 | 
					  return bytes_in_parse_buf >= UBLOX_HEADER_SIZE + UBLOX_CHECKSUM_SIZE && | 
				
			||||||
 | 
					    needed_bytes() == 0 && | 
				
			||||||
 | 
					    valid_cheksum(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline bool UbloxMsgParser::valid_so_far() { | 
				
			||||||
 | 
					  if(bytes_in_parse_buf > 0 && msg_parse_buf[0] != PREAMBLE1) { | 
				
			||||||
 | 
					    //LOGD("PREAMBLE1 invalid, %02X.", msg_parse_buf[0]);
 | 
				
			||||||
 | 
					    return false; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  if(bytes_in_parse_buf > 1 && msg_parse_buf[1] != PREAMBLE2) { | 
				
			||||||
 | 
					    //LOGD("PREAMBLE2 invalid, %02X.", msg_parse_buf[1]);
 | 
				
			||||||
 | 
					    return false; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  if(needed_bytes() == 0 && !valid()) | 
				
			||||||
 | 
					    return false; | 
				
			||||||
 | 
					  return true; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kj::Array<capnp::word> UbloxMsgParser::gen_solution() { | 
				
			||||||
 | 
					  nav_pvt_msg *msg = (nav_pvt_msg *)&msg_parse_buf[UBLOX_HEADER_SIZE]; | 
				
			||||||
 | 
					  capnp::MallocMessageBuilder msg_builder; | 
				
			||||||
 | 
					  cereal::Event::Builder event = msg_builder.initRoot<cereal::Event>(); | 
				
			||||||
 | 
					  event.setLogMonoTime(nanos_since_boot()); | 
				
			||||||
 | 
					  auto gpsLoc = event.initGpsLocationExternal(); | 
				
			||||||
 | 
					  gpsLoc.setSource(cereal::GpsLocationData::SensorSource::UBLOX); | 
				
			||||||
 | 
					  gpsLoc.setFlags(msg->flags); | 
				
			||||||
 | 
					  gpsLoc.setLatitude(msg->lat * 1e-07); | 
				
			||||||
 | 
					  gpsLoc.setLongitude(msg->lon * 1e-07); | 
				
			||||||
 | 
					  gpsLoc.setAltitude(msg->height * 1e-03); | 
				
			||||||
 | 
					  gpsLoc.setSpeed(msg->gSpeed * 1e-03); | 
				
			||||||
 | 
					  gpsLoc.setBearing(msg->headMot * 1e-5); | 
				
			||||||
 | 
					  gpsLoc.setAccuracy(msg->hAcc * 1e-03); | 
				
			||||||
 | 
					  std::tm timeinfo = std::tm(); | 
				
			||||||
 | 
					  timeinfo.tm_year = msg->year - 1900; | 
				
			||||||
 | 
					  timeinfo.tm_mon = msg->month - 1; | 
				
			||||||
 | 
					  timeinfo.tm_mday = msg->day; | 
				
			||||||
 | 
					  timeinfo.tm_hour = msg->hour; | 
				
			||||||
 | 
					  timeinfo.tm_min = msg->min; | 
				
			||||||
 | 
					  timeinfo.tm_sec = msg->sec; | 
				
			||||||
 | 
					  std::time_t utc_tt = timegm(&timeinfo); | 
				
			||||||
 | 
					  gpsLoc.setTimestamp(utc_tt * 1e+03 + msg->nano * 1e-06); | 
				
			||||||
 | 
					  float f[] = { msg->velN * 1e-03f, msg->velE * 1e-03f, msg->velD * 1e-03f }; | 
				
			||||||
 | 
					  kj::ArrayPtr<const float> ap(&f[0], sizeof(f) / sizeof(f[0])); | 
				
			||||||
 | 
					  gpsLoc.setVNED(ap); | 
				
			||||||
 | 
					  gpsLoc.setVerticalAccuracy(msg->vAcc * 1e-03); | 
				
			||||||
 | 
					  gpsLoc.setSpeedAccuracy(msg->sAcc * 1e-03); | 
				
			||||||
 | 
					  gpsLoc.setBearingAccuracy(msg->headAcc * 1e-05); | 
				
			||||||
 | 
					  return capnp::messageToFlatArray(msg_builder); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline bool bit_to_bool(uint8_t val, int shifts) { | 
				
			||||||
 | 
					  return (val & (1 << shifts)) ? true : false; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kj::Array<capnp::word> UbloxMsgParser::gen_raw() { | 
				
			||||||
 | 
					  rxm_raw_msg *msg = (rxm_raw_msg *)&msg_parse_buf[UBLOX_HEADER_SIZE]; | 
				
			||||||
 | 
					  if(bytes_in_parse_buf != ( | 
				
			||||||
 | 
					    UBLOX_HEADER_SIZE + sizeof(rxm_raw_msg) + msg->numMeas * sizeof(rxm_raw_msg_extra) + UBLOX_CHECKSUM_SIZE | 
				
			||||||
 | 
					    )) { | 
				
			||||||
 | 
					    LOGD("Invalid measurement size %u, %u, %u, %u", msg->numMeas, bytes_in_parse_buf, sizeof(rxm_raw_msg_extra), sizeof(rxm_raw_msg)); | 
				
			||||||
 | 
					    return kj::Array<capnp::word>(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  rxm_raw_msg_extra *measurements = (rxm_raw_msg_extra *)&msg_parse_buf[UBLOX_HEADER_SIZE + sizeof(rxm_raw_msg)]; | 
				
			||||||
 | 
					  capnp::MallocMessageBuilder msg_builder; | 
				
			||||||
 | 
					  cereal::Event::Builder event = msg_builder.initRoot<cereal::Event>(); | 
				
			||||||
 | 
					  event.setLogMonoTime(nanos_since_boot()); | 
				
			||||||
 | 
					  auto gnss = event.initUbloxGnss(); | 
				
			||||||
 | 
					  auto mr = gnss.initMeasurementReport(); | 
				
			||||||
 | 
					  mr.setRcvTow(msg->rcvTow); | 
				
			||||||
 | 
					  mr.setGpsWeek(msg->week); | 
				
			||||||
 | 
					  mr.setLeapSeconds(msg->leapS); | 
				
			||||||
 | 
					  mr.setGpsWeek(msg->week); | 
				
			||||||
 | 
					  auto mb = mr.initMeasurements(msg->numMeas); | 
				
			||||||
 | 
					  for(int8_t i = 0; i < msg->numMeas; i++) { | 
				
			||||||
 | 
					    mb[i].setSvId(measurements[i].svId); | 
				
			||||||
 | 
					    mb[i].setSigId(measurements[i].sigId); | 
				
			||||||
 | 
					    mb[i].setPseudorange(measurements[i].prMes); | 
				
			||||||
 | 
					    mb[i].setCarrierCycles(measurements[i].cpMes); | 
				
			||||||
 | 
					    mb[i].setDoppler(measurements[i].doMes); | 
				
			||||||
 | 
					    mb[i].setGnssId(measurements[i].gnssId); | 
				
			||||||
 | 
					    mb[i].setGlonassFrequencyIndex(measurements[i].freqId); | 
				
			||||||
 | 
					    mb[i].setLocktime(measurements[i].locktime); | 
				
			||||||
 | 
					    mb[i].setCno(measurements[i].cno); | 
				
			||||||
 | 
					    mb[i].setPseudorangeStdev(0.01*(pow(2, (measurements[i].prStdev & 15)))); // weird scaling, might be wrong
 | 
				
			||||||
 | 
					    mb[i].setCarrierPhaseStdev(0.004*(measurements[i].cpStdev & 15)); | 
				
			||||||
 | 
					    mb[i].setDopplerStdev(0.002*(pow(2, (measurements[i].doStdev & 15)))); // weird scaling, might be wrong
 | 
				
			||||||
 | 
					    auto ts = mb[i].initTrackingStatus(); | 
				
			||||||
 | 
					    ts.setPseudorangeValid(bit_to_bool(measurements[i].trkStat, 0)); | 
				
			||||||
 | 
					    ts.setCarrierPhaseValid(bit_to_bool(measurements[i].trkStat, 1)); | 
				
			||||||
 | 
					    ts.setHalfCycleValid(bit_to_bool(measurements[i].trkStat, 2)); | 
				
			||||||
 | 
					    ts.setHalfCycleSubtracted(bit_to_bool(measurements[i].trkStat, 3)); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mr.setNumMeas(msg->numMeas); | 
				
			||||||
 | 
					  auto rs = mr.initReceiverStatus(); | 
				
			||||||
 | 
					  rs.setLeapSecValid(bit_to_bool(msg->recStat, 0)); | 
				
			||||||
 | 
					  rs.setClkReset(bit_to_bool(msg->recStat, 2)); | 
				
			||||||
 | 
					  return capnp::messageToFlatArray(msg_builder); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kj::Array<capnp::word> UbloxMsgParser::gen_nav_data() { | 
				
			||||||
 | 
					  rxm_sfrbx_msg *msg = (rxm_sfrbx_msg *)&msg_parse_buf[UBLOX_HEADER_SIZE]; | 
				
			||||||
 | 
					  if(bytes_in_parse_buf != ( | 
				
			||||||
 | 
					    UBLOX_HEADER_SIZE + sizeof(rxm_sfrbx_msg) + msg->numWords * sizeof(rxm_sfrbx_msg_extra) + UBLOX_CHECKSUM_SIZE | 
				
			||||||
 | 
					    )) { | 
				
			||||||
 | 
					    LOGD("Invalid sfrbx words size %u, %u, %u, %u", msg->numWords, bytes_in_parse_buf, sizeof(rxm_raw_msg_extra), sizeof(rxm_raw_msg)); | 
				
			||||||
 | 
					    return kj::Array<capnp::word>(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  rxm_sfrbx_msg_extra *measurements = (rxm_sfrbx_msg_extra *)&msg_parse_buf[UBLOX_HEADER_SIZE + sizeof(rxm_sfrbx_msg)]; | 
				
			||||||
 | 
					  if(msg->gnssId  == 0) { | 
				
			||||||
 | 
					    uint8_t subframeId =  GET_FIELD_U(measurements[1].dwrd, 3, 8); | 
				
			||||||
 | 
					    std::vector<uint32_t> words; | 
				
			||||||
 | 
					    for(int i = 0; i < msg->numWords;i++) | 
				
			||||||
 | 
					      words.push_back(measurements[i].dwrd); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(subframeId == 1) { | 
				
			||||||
 | 
					      nav_frame_buffer[msg->gnssId][msg->svid] = subframes_map(); | 
				
			||||||
 | 
					      nav_frame_buffer[msg->gnssId][msg->svid][subframeId] = words; | 
				
			||||||
 | 
					    } else if(nav_frame_buffer[msg->gnssId][msg->svid].find(subframeId-1) != nav_frame_buffer[msg->gnssId][msg->svid].end()) | 
				
			||||||
 | 
					      nav_frame_buffer[msg->gnssId][msg->svid][subframeId] = words; | 
				
			||||||
 | 
					    if(nav_frame_buffer[msg->gnssId][msg->svid].size() == 5) { | 
				
			||||||
 | 
					      EphemerisData ephem_data(msg->svid, nav_frame_buffer[msg->gnssId][msg->svid]); | 
				
			||||||
 | 
					      capnp::MallocMessageBuilder msg_builder; | 
				
			||||||
 | 
					      cereal::Event::Builder event = msg_builder.initRoot<cereal::Event>(); | 
				
			||||||
 | 
					      event.setLogMonoTime(nanos_since_boot()); | 
				
			||||||
 | 
					      auto gnss = event.initUbloxGnss(); | 
				
			||||||
 | 
					      auto eph = gnss.initEphemeris(); | 
				
			||||||
 | 
					      eph.setSvId(ephem_data.svId); | 
				
			||||||
 | 
					      eph.setToc(ephem_data.toc); | 
				
			||||||
 | 
					      eph.setGpsWeek(ephem_data.gpsWeek); | 
				
			||||||
 | 
					      eph.setAf0(ephem_data.af0); | 
				
			||||||
 | 
					      eph.setAf1(ephem_data.af1); | 
				
			||||||
 | 
					      eph.setAf2(ephem_data.af2); | 
				
			||||||
 | 
					      eph.setIode(ephem_data.iode); | 
				
			||||||
 | 
					      eph.setCrs(ephem_data.crs); | 
				
			||||||
 | 
					      eph.setDeltaN(ephem_data.deltaN); | 
				
			||||||
 | 
					      eph.setM0(ephem_data.M0); | 
				
			||||||
 | 
					      eph.setCuc(ephem_data.cuc); | 
				
			||||||
 | 
					      eph.setEcc(ephem_data.ecc); | 
				
			||||||
 | 
					      eph.setCus(ephem_data.cus); | 
				
			||||||
 | 
					      eph.setA(ephem_data.A); | 
				
			||||||
 | 
					      eph.setToe(ephem_data.toe); | 
				
			||||||
 | 
					      eph.setCic(ephem_data.cic); | 
				
			||||||
 | 
					      eph.setOmega0(ephem_data.omega0); | 
				
			||||||
 | 
					      eph.setCis(ephem_data.cis); | 
				
			||||||
 | 
					      eph.setI0(ephem_data.i0); | 
				
			||||||
 | 
					      eph.setCrc(ephem_data.crc); | 
				
			||||||
 | 
					      eph.setOmega(ephem_data.omega); | 
				
			||||||
 | 
					      eph.setOmegaDot(ephem_data.omega_dot); | 
				
			||||||
 | 
					      eph.setIDot(ephem_data.idot); | 
				
			||||||
 | 
					      eph.setTgd(ephem_data.Tgd); | 
				
			||||||
 | 
					      eph.setIonoCoeffsValid(ephem_data.ionoCoeffsValid); | 
				
			||||||
 | 
					      if(ephem_data.ionoCoeffsValid) { | 
				
			||||||
 | 
					        kj::ArrayPtr<const double> apa(&ephem_data.ionoAlpha[0], sizeof(ephem_data.ionoAlpha) / sizeof(ephem_data.ionoAlpha[0])); | 
				
			||||||
 | 
					        eph.setIonoAlpha(apa); | 
				
			||||||
 | 
					        kj::ArrayPtr<const double> apb(&ephem_data.ionoBeta[0], sizeof(ephem_data.ionoBeta) / sizeof(ephem_data.ionoBeta[0])); | 
				
			||||||
 | 
					        eph.setIonoBeta(apb); | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        eph.setIonoAlpha(kj::ArrayPtr<const double>()); | 
				
			||||||
 | 
					        eph.setIonoBeta(kj::ArrayPtr<const double>()); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return capnp::messageToFlatArray(msg_builder); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return kj::Array<capnp::word>(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool UbloxMsgParser::add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { | 
				
			||||||
 | 
					  int needed = needed_bytes(); | 
				
			||||||
 | 
					  if(needed > 0) { | 
				
			||||||
 | 
					    bytes_consumed = min((size_t)needed, incoming_data_len ); | 
				
			||||||
 | 
					    // Add data to buffer
 | 
				
			||||||
 | 
					    memcpy(msg_parse_buf + bytes_in_parse_buf, incoming_data, bytes_consumed); | 
				
			||||||
 | 
					    bytes_in_parse_buf += bytes_consumed; | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    bytes_consumed = incoming_data_len; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  // Validate msg format, detect invalid header and invalid checksum.
 | 
				
			||||||
 | 
					  while(!valid_so_far() && bytes_in_parse_buf != 0) { | 
				
			||||||
 | 
					    //LOGD("Drop corrupt data, remained in buf: %u", bytes_in_parse_buf);
 | 
				
			||||||
 | 
					    // Corrupted msg, drop a byte.
 | 
				
			||||||
 | 
					    bytes_in_parse_buf -= 1; | 
				
			||||||
 | 
					    if(bytes_in_parse_buf > 0) | 
				
			||||||
 | 
					      memmove(&msg_parse_buf[0], &msg_parse_buf[1], bytes_in_parse_buf); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  // There is redundant data at the end of buffer, reset the buffer.
 | 
				
			||||||
 | 
					  if(needed_bytes() == -1) | 
				
			||||||
 | 
					    bytes_in_parse_buf = 0; | 
				
			||||||
 | 
					  return valid(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,150 @@ | 
				
			|||||||
 | 
					#pragma once | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h> | 
				
			||||||
 | 
					#include "messaging.hpp" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define min(x, y) ((x) <= (y) ? (x) : (y)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NAV_PVT
 | 
				
			||||||
 | 
					typedef struct __attribute__((packed)) { | 
				
			||||||
 | 
					  uint32_t iTOW; | 
				
			||||||
 | 
					  uint16_t year; | 
				
			||||||
 | 
					  int8_t month; | 
				
			||||||
 | 
					  int8_t day; | 
				
			||||||
 | 
					  int8_t hour; | 
				
			||||||
 | 
					  int8_t min; | 
				
			||||||
 | 
					  int8_t sec; | 
				
			||||||
 | 
					  int8_t valid; | 
				
			||||||
 | 
					  uint32_t tAcc; | 
				
			||||||
 | 
					  int32_t nano; | 
				
			||||||
 | 
					  int8_t fixType; | 
				
			||||||
 | 
					  int8_t flags; | 
				
			||||||
 | 
					  int8_t flags2; | 
				
			||||||
 | 
					  int8_t numSV; | 
				
			||||||
 | 
					  int32_t lon; | 
				
			||||||
 | 
					  int32_t lat; | 
				
			||||||
 | 
					  int32_t height; | 
				
			||||||
 | 
					  int32_t hMSL; | 
				
			||||||
 | 
					  uint32_t hAcc; | 
				
			||||||
 | 
					  uint32_t vAcc; | 
				
			||||||
 | 
					  int32_t velN; | 
				
			||||||
 | 
					  int32_t velE; | 
				
			||||||
 | 
					  int32_t velD; | 
				
			||||||
 | 
					  int32_t gSpeed; | 
				
			||||||
 | 
					  int32_t headMot; | 
				
			||||||
 | 
					  uint32_t sAcc; | 
				
			||||||
 | 
					  uint32_t headAcc; | 
				
			||||||
 | 
					  uint16_t pDOP; | 
				
			||||||
 | 
					  int8_t reserverd1[6]; | 
				
			||||||
 | 
					  int32_t headVeh; | 
				
			||||||
 | 
					  int16_t magDec; | 
				
			||||||
 | 
					  uint16_t magAcc; | 
				
			||||||
 | 
					} nav_pvt_msg; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RXM_RAW
 | 
				
			||||||
 | 
					typedef struct __attribute__((packed)) { | 
				
			||||||
 | 
					  double rcvTow; | 
				
			||||||
 | 
					  uint16_t week; | 
				
			||||||
 | 
					  int8_t leapS; | 
				
			||||||
 | 
					  int8_t numMeas; | 
				
			||||||
 | 
					  int8_t recStat; | 
				
			||||||
 | 
					  int8_t reserved1[3]; | 
				
			||||||
 | 
					} rxm_raw_msg; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extra data count is in numMeas
 | 
				
			||||||
 | 
					typedef struct __attribute__((packed)) { | 
				
			||||||
 | 
					  double prMes; | 
				
			||||||
 | 
					  double cpMes; | 
				
			||||||
 | 
					  float doMes; | 
				
			||||||
 | 
					  int8_t gnssId; | 
				
			||||||
 | 
					  int8_t svId; | 
				
			||||||
 | 
					  int8_t sigId; | 
				
			||||||
 | 
					  int8_t freqId; | 
				
			||||||
 | 
					  uint16_t locktime; | 
				
			||||||
 | 
					  int8_t cno; | 
				
			||||||
 | 
					  int8_t prStdev; | 
				
			||||||
 | 
					  int8_t cpStdev; | 
				
			||||||
 | 
					  int8_t doStdev; | 
				
			||||||
 | 
					  int8_t trkStat; | 
				
			||||||
 | 
					  int8_t reserved3; | 
				
			||||||
 | 
					} rxm_raw_msg_extra; | 
				
			||||||
 | 
					// RXM_SFRBX
 | 
				
			||||||
 | 
					typedef struct __attribute__((packed)) { | 
				
			||||||
 | 
					  int8_t gnssId; | 
				
			||||||
 | 
					  int8_t svid; | 
				
			||||||
 | 
					  int8_t reserved1; | 
				
			||||||
 | 
					  int8_t freqId; | 
				
			||||||
 | 
					  int8_t numWords; | 
				
			||||||
 | 
					  int8_t reserved2; | 
				
			||||||
 | 
					  int8_t version; | 
				
			||||||
 | 
					  int8_t reserved3; | 
				
			||||||
 | 
					} rxm_sfrbx_msg; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extra data count is in numWords
 | 
				
			||||||
 | 
					typedef struct __attribute__((packed)) { | 
				
			||||||
 | 
					  uint32_t dwrd; | 
				
			||||||
 | 
					} rxm_sfrbx_msg_extra; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ublox { | 
				
			||||||
 | 
					  // protocol constants
 | 
				
			||||||
 | 
					  const uint8_t PREAMBLE1 = 0xb5; | 
				
			||||||
 | 
					  const uint8_t PREAMBLE2 = 0x62; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // message classes
 | 
				
			||||||
 | 
					  const uint8_t CLASS_NAV = 0x01; | 
				
			||||||
 | 
					  const uint8_t CLASS_RXM = 0x02; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // NAV messages
 | 
				
			||||||
 | 
					  const uint8_t MSG_NAV_PVT = 0x7; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // RXM messages
 | 
				
			||||||
 | 
					  const uint8_t MSG_RXM_RAW = 0x15; | 
				
			||||||
 | 
					  const uint8_t MSG_RXM_SFRBX = 0x13; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const int UBLOX_HEADER_SIZE = 6; | 
				
			||||||
 | 
					  const int UBLOX_CHECKSUM_SIZE = 2; | 
				
			||||||
 | 
					  const int UBLOX_MAX_MSG_SIZE = 65536; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  typedef std::map<uint8_t, std::vector<uint32_t>> subframes_map; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class UbloxMsgParser { | 
				
			||||||
 | 
					    public: | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      UbloxMsgParser(); | 
				
			||||||
 | 
					      kj::Array<capnp::word> gen_solution(); | 
				
			||||||
 | 
					      kj::Array<capnp::word> gen_raw(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      kj::Array<capnp::word> gen_nav_data(); | 
				
			||||||
 | 
					      bool add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed); | 
				
			||||||
 | 
					      inline void reset() {bytes_in_parse_buf = 0;} | 
				
			||||||
 | 
					      inline uint8_t msg_class() { | 
				
			||||||
 | 
					        return msg_parse_buf[2]; | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      inline uint8_t msg_id() { | 
				
			||||||
 | 
					        return msg_parse_buf[3]; | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      inline int needed_bytes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      void hexdump(uint8_t *d, int l) { | 
				
			||||||
 | 
					        for (int i = 0; i < l; i++) { | 
				
			||||||
 | 
					          if (i%0x10 == 0 && i != 0) printf("\n"); | 
				
			||||||
 | 
					          printf("%02X ", d[i]); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        printf("\n"); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    private: | 
				
			||||||
 | 
					      inline bool valid_cheksum(); | 
				
			||||||
 | 
					      inline bool valid(); | 
				
			||||||
 | 
					      inline bool valid_so_far(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint8_t msg_parse_buf[UBLOX_HEADER_SIZE + UBLOX_MAX_MSG_SIZE]; | 
				
			||||||
 | 
					      int bytes_in_parse_buf; | 
				
			||||||
 | 
					      std::map<uint8_t, std::map<uint8_t, subframes_map>> nav_frame_buffer; | 
				
			||||||
 | 
					  }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Message * (*poll_ubloxraw_msg_func)(Poller *poller); | 
				
			||||||
 | 
					typedef int (*send_gps_event_func)(PubSocket *s, const void *buf, size_t len); | 
				
			||||||
 | 
					int ubloxd_main(poll_ubloxraw_msg_func poll_func, send_gps_event_func send_func); | 
				
			||||||
@ -0,0 +1,47 @@ | 
				
			|||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include <stdlib.h> | 
				
			||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include <signal.h> | 
				
			||||||
 | 
					#include <unistd.h> | 
				
			||||||
 | 
					#include <sched.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <sys/cdefs.h> | 
				
			||||||
 | 
					#include <sys/types.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <assert.h> | 
				
			||||||
 | 
					#include <math.h> | 
				
			||||||
 | 
					#include <ctime> | 
				
			||||||
 | 
					#include <chrono> | 
				
			||||||
 | 
					#include <map> | 
				
			||||||
 | 
					#include <vector> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "messaging.hpp" | 
				
			||||||
 | 
					#include <capnp/serialize.h> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/params.h" | 
				
			||||||
 | 
					#include "common/swaglog.h" | 
				
			||||||
 | 
					#include "common/timing.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ublox_msg.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const long ZMQ_POLL_TIMEOUT = 1000; // In miliseconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Message * poll_ubloxraw_msg(Poller * poller) { | 
				
			||||||
 | 
					  auto p = poller->poll(ZMQ_POLL_TIMEOUT); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (p.size()) { | 
				
			||||||
 | 
					    return p[0]->receive(); | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    return NULL; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int send_gps_event(PubSocket *s, const void *buf, size_t len) { | 
				
			||||||
 | 
					  return s->send((char*)buf, len); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main() { | 
				
			||||||
 | 
					  return ubloxd_main(poll_ubloxraw_msg, send_gps_event); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,115 @@ | 
				
			|||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include <stdlib.h> | 
				
			||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include <signal.h> | 
				
			||||||
 | 
					#include <unistd.h> | 
				
			||||||
 | 
					#include <sched.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <sys/cdefs.h> | 
				
			||||||
 | 
					#include <sys/types.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <assert.h> | 
				
			||||||
 | 
					#include <math.h> | 
				
			||||||
 | 
					#include <ctime> | 
				
			||||||
 | 
					#include <chrono> | 
				
			||||||
 | 
					#include <map> | 
				
			||||||
 | 
					#include <vector> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "messaging.hpp" | 
				
			||||||
 | 
					#include <capnp/serialize.h> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/params.h" | 
				
			||||||
 | 
					#include "common/swaglog.h" | 
				
			||||||
 | 
					#include "common/timing.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ublox_msg.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					volatile sig_atomic_t do_exit = 0; // Flag for process exit on signal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void set_do_exit(int sig) { | 
				
			||||||
 | 
					  do_exit = 1; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ublox; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ubloxd_main(poll_ubloxraw_msg_func poll_func, send_gps_event_func send_func) { | 
				
			||||||
 | 
					  LOGW("starting ubloxd"); | 
				
			||||||
 | 
					  signal(SIGINT, (sighandler_t) set_do_exit); | 
				
			||||||
 | 
					  signal(SIGTERM, (sighandler_t) set_do_exit); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  UbloxMsgParser parser; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Context * c = Context::create(); | 
				
			||||||
 | 
					  PubSocket * gpsLocationExternal = PubSocket::create(c, "gpsLocationExternal"); | 
				
			||||||
 | 
					  PubSocket * ubloxGnss = PubSocket::create(c, "ubloxGnss"); | 
				
			||||||
 | 
					  SubSocket * ubloxRaw = SubSocket::create(c, "ubloxRaw"); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  assert(gpsLocationExternal != NULL); | 
				
			||||||
 | 
					  assert(ubloxGnss != NULL); | 
				
			||||||
 | 
					  assert(ubloxRaw != NULL); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Poller * poller = Poller::create({ubloxRaw}); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (!do_exit) { | 
				
			||||||
 | 
					    Message * msg = poll_func(poller); | 
				
			||||||
 | 
					    if (msg == NULL) continue; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto amsg = kj::heapArray<capnp::word>((msg->getSize() / sizeof(capnp::word)) + 1); | 
				
			||||||
 | 
					    memcpy(amsg.begin(), msg->getData(), msg->getSize()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    capnp::FlatArrayMessageReader cmsg(amsg); | 
				
			||||||
 | 
					    cereal::Event::Reader event = cmsg.getRoot<cereal::Event>(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint8_t *data = event.getUbloxRaw().begin(); | 
				
			||||||
 | 
					    size_t len = event.getUbloxRaw().size(); | 
				
			||||||
 | 
					    size_t bytes_consumed = 0; | 
				
			||||||
 | 
					    while(bytes_consumed < len && !do_exit) { | 
				
			||||||
 | 
					      size_t bytes_consumed_this_time = 0U; | 
				
			||||||
 | 
					      if(parser.add_data(data + bytes_consumed, (uint32_t)(len - bytes_consumed), bytes_consumed_this_time)) { | 
				
			||||||
 | 
					        // New message available
 | 
				
			||||||
 | 
					        if(parser.msg_class() == CLASS_NAV) { | 
				
			||||||
 | 
					          if(parser.msg_id() == MSG_NAV_PVT) { | 
				
			||||||
 | 
					            //LOGD("MSG_NAV_PVT");
 | 
				
			||||||
 | 
					            auto words = parser.gen_solution(); | 
				
			||||||
 | 
					            if(words.size() > 0) { | 
				
			||||||
 | 
					              auto bytes = words.asBytes(); | 
				
			||||||
 | 
					              send_func(gpsLocationExternal, bytes.begin(), bytes.size()); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					          } else | 
				
			||||||
 | 
					            LOGW("Unknown nav msg id: 0x%02X", parser.msg_id()); | 
				
			||||||
 | 
					        } else if(parser.msg_class() == CLASS_RXM) { | 
				
			||||||
 | 
					          if(parser.msg_id() == MSG_RXM_RAW) { | 
				
			||||||
 | 
					            //LOGD("MSG_RXM_RAW");
 | 
				
			||||||
 | 
					            auto words = parser.gen_raw(); | 
				
			||||||
 | 
					            if(words.size() > 0) { | 
				
			||||||
 | 
					              auto bytes = words.asBytes(); | 
				
			||||||
 | 
					              send_func(ubloxGnss, bytes.begin(), bytes.size()); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					          } else if(parser.msg_id() == MSG_RXM_SFRBX) { | 
				
			||||||
 | 
					            //LOGD("MSG_RXM_SFRBX");
 | 
				
			||||||
 | 
					            auto words = parser.gen_nav_data(); | 
				
			||||||
 | 
					            if(words.size() > 0) { | 
				
			||||||
 | 
					              auto bytes = words.asBytes(); | 
				
			||||||
 | 
					              send_func(ubloxGnss, bytes.begin(), bytes.size()); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					          } else | 
				
			||||||
 | 
					            LOGW("Unknown rxm msg id: 0x%02X", parser.msg_id()); | 
				
			||||||
 | 
					        } else | 
				
			||||||
 | 
					          LOGW("Unknown msg class: 0x%02X", parser.msg_class()); | 
				
			||||||
 | 
					        parser.reset(); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      bytes_consumed += bytes_consumed_this_time; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    delete msg; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete poller; | 
				
			||||||
 | 
					  delete ubloxRaw; | 
				
			||||||
 | 
					  delete ubloxGnss; | 
				
			||||||
 | 
					  delete gpsLocationExternal; | 
				
			||||||
 | 
					  delete c; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 0; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,102 @@ | 
				
			|||||||
 | 
					#include <stdio.h> | 
				
			||||||
 | 
					#include <stdlib.h> | 
				
			||||||
 | 
					#include <string.h> | 
				
			||||||
 | 
					#include <signal.h> | 
				
			||||||
 | 
					#include <unistd.h> | 
				
			||||||
 | 
					#include <sched.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <sys/cdefs.h> | 
				
			||||||
 | 
					#include <sys/types.h> | 
				
			||||||
 | 
					#include <sys/time.h> | 
				
			||||||
 | 
					#include <assert.h> | 
				
			||||||
 | 
					#include <math.h> | 
				
			||||||
 | 
					#include <ctime> | 
				
			||||||
 | 
					#include <chrono> | 
				
			||||||
 | 
					#include <map> | 
				
			||||||
 | 
					#include <vector> | 
				
			||||||
 | 
					#include <iostream> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "messaging.hpp" | 
				
			||||||
 | 
					#include "impl_zmq.hpp" | 
				
			||||||
 | 
					#include <capnp/serialize.h> | 
				
			||||||
 | 
					#include "cereal/gen/cpp/log.capnp.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "common/params.h" | 
				
			||||||
 | 
					#include "common/swaglog.h" | 
				
			||||||
 | 
					#include "common/timing.h" | 
				
			||||||
 | 
					#include "common/util.h" | 
				
			||||||
 | 
					#include "ublox_msg.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ublox; | 
				
			||||||
 | 
					extern volatile sig_atomic_t do_exit; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void write_file(std::string fpath, uint8_t *data, int len) { | 
				
			||||||
 | 
					  FILE* f = fopen(fpath.c_str(), "wb"); | 
				
			||||||
 | 
					  if (!f) { | 
				
			||||||
 | 
					    std::cout << "Open " << fpath << " failed" << std::endl; | 
				
			||||||
 | 
					    return; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  fwrite(data, len, 1, f); | 
				
			||||||
 | 
					  fclose(f); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static size_t len = 0U; | 
				
			||||||
 | 
					static size_t consumed = 0U; | 
				
			||||||
 | 
					static uint8_t *data = NULL; | 
				
			||||||
 | 
					static int save_idx = 0; | 
				
			||||||
 | 
					static std::string prefix; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Message * poll_ubloxraw_msg(Poller * poller) { | 
				
			||||||
 | 
					  assert(poller); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t consuming  = min(len - consumed, 128); | 
				
			||||||
 | 
					  if(consumed < len) { | 
				
			||||||
 | 
					    // create message
 | 
				
			||||||
 | 
					    capnp::MallocMessageBuilder msg_builder; | 
				
			||||||
 | 
					    cereal::Event::Builder event = msg_builder.initRoot<cereal::Event>(); | 
				
			||||||
 | 
					    event.setLogMonoTime(nanos_since_boot()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto ublox_raw = event.initUbloxRaw(consuming); | 
				
			||||||
 | 
					    memcpy(ublox_raw.begin(), (void *)(data + consumed), consuming); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto words = capnp::messageToFlatArray(msg_builder); | 
				
			||||||
 | 
					    auto bytes = words.asBytes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Message * msg = new ZMQMessage(); | 
				
			||||||
 | 
					    msg->init((char*)bytes.begin(), bytes.size()); | 
				
			||||||
 | 
					    consumed += consuming; | 
				
			||||||
 | 
					    return msg; | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    do_exit = 1; | 
				
			||||||
 | 
					    return NULL; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int send_gps_event(PubSocket *s, const void *buf, size_t len) { | 
				
			||||||
 | 
					  assert(s); | 
				
			||||||
 | 
					  write_file(prefix + "/" + std::to_string(save_idx), (uint8_t *)buf, len); | 
				
			||||||
 | 
					  save_idx++; | 
				
			||||||
 | 
					  return len; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char** argv) { | 
				
			||||||
 | 
					  if(argc < 3) { | 
				
			||||||
 | 
					    printf("Format: ubloxd_test stream_file_path save_prefix\n"); | 
				
			||||||
 | 
					    return 0; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  // Parse 11360 msgs, generate 9452 events
 | 
				
			||||||
 | 
					  data = (uint8_t *)read_file(argv[1], &len); | 
				
			||||||
 | 
					  if(data == NULL) { | 
				
			||||||
 | 
					    LOGE("Read file %s failed\n", argv[1]); | 
				
			||||||
 | 
					    return -1; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  prefix = argv[2]; | 
				
			||||||
 | 
					  ubloxd_main(poll_ubloxraw_msg, send_gps_event); | 
				
			||||||
 | 
					  free(data); | 
				
			||||||
 | 
					  printf("Generated %d cereal events\n", save_idx); | 
				
			||||||
 | 
					  if(save_idx != 9452) { | 
				
			||||||
 | 
					    printf("Event count error: %d\n", save_idx); | 
				
			||||||
 | 
					    return -1; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return 0; | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue