[IMU_Cal] Runtime sensor calibration code update.

This code transfers all of the refactored changes developed and
tested in Google3.

Tip of G3 CL: 191014272
Bug: 75333210
Test: ./load_app.sh and verified calibrations on device.
Change-Id: Ieb414468296ce620ecf7fe275595c561c33779c1
diff --git a/firmware/os/algos/calibration/accelerometer/accel_cal.c b/firmware/os/algos/calibration/accelerometer/accel_cal.c
index da402ea..3c3160f 100644
--- a/firmware/os/algos/calibration/accelerometer/accel_cal.c
+++ b/firmware/os/algos/calibration/accelerometer/accel_cal.c
@@ -15,13 +15,15 @@
  */
 
 #include "calibration/accelerometer/accel_cal.h"
+
 #include <errno.h>
 #include <math.h>
 #include <stdio.h>
 #include <string.h>
-#include "calibration/magnetometer/mag_cal.h"
+
 #include "calibration/util/cal_log.h"
 
+// clang-format off
 #define KSCALE \
   0.101936799f         // Scaling from m/s^2 to g (0.101 = 1/(9.81 m/s^2)).
 #define KSCALE2 9.81f  // Scaling from g to m/s^2.
@@ -38,6 +40,7 @@
 #define TEMP_CUT 30     // Separation point for temperature buckets 30 degree C.
 #define EIGEN_RATIO 0.35f  // EIGEN_RATIO (must be greater than 0.35).
 #define EIGEN_MAG 0.97f    // Eigen value magnitude (must be greater than 0.97).
+#define ACCEL_NEW_BIAS_THRESHOLD (0.0f)  // Bias update detection threshold.
 #ifdef ACCEL_CAL_DBG_ENABLED
 #define TEMP_HIST_LOW \
   16  // Putting all Temp counts in first bucket for temp < 16 degree C.
@@ -47,8 +50,9 @@
 #endif
 #ifdef IMU_TEMP_DBG_ENABLED
 #define IMU_TEMP_DELTA_TIME_NANOS \
-  5000000000 // Printing every 5 seconds IMU temp.
+  5000000000   // Printing every 5 seconds IMU temp.
 #endif
+// clang-format on
 
 /////////// Start Debug //////////////////////
 
@@ -159,19 +163,29 @@
                              uint32_t fxb, uint32_t fy, uint32_t fyb,
                              uint32_t fz, uint32_t fzb, uint32_t fle) {
   accelGoodDataInit(&acc->agd, fx, fxb, fy, fyb, fz, fzb, fle);
-  initKasa(&acc->akf);
+  kasaInit(&acc->akf);
+}
+
+// Returns true when a new accel calibration is available.
+bool accelCalNewBiasAvailable(struct AccelCal *acc) {
+  return fabsf(acc->x_bias - acc->x_bias_new) > ACCEL_NEW_BIAS_THRESHOLD ||
+         fabsf(acc->y_bias - acc->y_bias_new) > ACCEL_NEW_BIAS_THRESHOLD ||
+         fabsf(acc->z_bias - acc->z_bias_new) > ACCEL_NEW_BIAS_THRESHOLD;
 }
 
 // Accel cal init.
-void accelCalInit(struct AccelCal *acc, uint32_t t0, uint32_t n_s, float th,
-                  uint32_t fx, uint32_t fxb, uint32_t fy, uint32_t fyb,
-                  uint32_t fz, uint32_t fzb, uint32_t fle) {
+void accelCalInit(struct AccelCal *acc,
+                  const struct AccelCalParameters *parameters) {
   // Init core accel data.
-  accelCalAlgoInit(&acc->ac1[0], fx, fxb, fy, fyb, fz, fzb, fle);
-  accelCalAlgoInit(&acc->ac1[1], fx, fxb, fy, fyb, fz, fzb, fle);
+  accelCalAlgoInit(&acc->ac1[0], parameters->fx, parameters->fxb,
+                   parameters->fy, parameters->fyb, parameters->fz,
+                   parameters->fzb, parameters->fle);
+  accelCalAlgoInit(&acc->ac1[1], parameters->fx, parameters->fxb,
+                   parameters->fy, parameters->fyb, parameters->fz,
+                   parameters->fzb, parameters->fle);
 
   // Stillness Reset.
-  accelStillInit(&acc->asd, t0, n_s, th);
+  accelStillInit(&acc->asd, parameters->t0, parameters->n_s, parameters->th);
 
 // Debug data init.
 #ifdef ACCEL_CAL_DBG_ENABLED
@@ -267,31 +281,6 @@
   return complete;
 }
 
-// Accumulate data for KASA fit.
-static void accelCalUpdate(struct KasaFit *akf, struct AccelStillDet *asd) {
-  // Run accumulators.
-  float w = asd->mean_x * asd->mean_x + asd->mean_y * asd->mean_y +
-            asd->mean_z * asd->mean_z;
-
-  akf->acc_x += asd->mean_x;
-  akf->acc_y += asd->mean_y;
-  akf->acc_z += asd->mean_z;
-  akf->acc_w += w;
-
-  akf->acc_xx += asd->mean_x * asd->mean_x;
-  akf->acc_xy += asd->mean_x * asd->mean_y;
-  akf->acc_xz += asd->mean_x * asd->mean_z;
-  akf->acc_xw += asd->mean_x * w;
-
-  akf->acc_yy += asd->mean_y * asd->mean_y;
-  akf->acc_yz += asd->mean_y * asd->mean_z;
-  akf->acc_yw += asd->mean_y * w;
-
-  akf->acc_zz += asd->mean_z * asd->mean_z;
-  akf->acc_zw += asd->mean_z * w;
-  akf->nsamples += 1;
-}
-
 // Good data detection, sorting and accumulate the data for Kasa.
 static int accelGoodData(struct AccelStillDet *asd, struct AccelCalAlgo *ac1,
                          float temp) {
@@ -304,42 +293,42 @@
     ac1->agd.nx += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Negative x bucket nxb.
   if (PHIb > asd->mean_x && ac1->agd.nxb < ac1->agd.nfxb) {
     ac1->agd.nxb += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Y bucket ny.
   if (PHI < asd->mean_y && ac1->agd.ny < ac1->agd.nfy) {
     ac1->agd.ny += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Negative y bucket nyb.
   if (PHIb > asd->mean_y && ac1->agd.nyb < ac1->agd.nfyb) {
     ac1->agd.nyb += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Z bucket nz.
   if (PHIZ < asd->mean_z && ac1->agd.nz < ac1->agd.nfz) {
     ac1->agd.nz += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Negative z bucket nzb.
   if (PHIZb > asd->mean_z && ac1->agd.nzb < ac1->agd.nfzb) {
     ac1->agd.nzb += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // The leftover bucket nle.
   if (PHI > asd->mean_x && PHIb < asd->mean_x && PHI > asd->mean_y &&
@@ -348,7 +337,7 @@
     ac1->agd.nle += 1;
     ac1->agd.acc_t += temp;
     ac1->agd.acc_tt += temp * temp;
-    accelCalUpdate(&ac1->akf, asd);
+    kasaAccumulate(&ac1->akf, asd->mean_x, asd->mean_y, asd->mean_z);
   }
   // Checking if all buckets are full.
   if (ac1->agd.nx == ac1->agd.nfx && ac1->agd.nxb == ac1->agd.nfxb &&
@@ -357,32 +346,16 @@
     //  Check if akf->nsamples is zero.
     if (ac1->akf.nsamples == 0) {
       agdReset(&ac1->agd);
-      magKasaReset(&ac1->akf);
+      kasaReset(&ac1->akf);
       complete = 0;
       return complete;
-    } else {
-      // Normalize the data to the sample numbers.
-      inv = 1.0f / ac1->akf.nsamples;
     }
 
-    ac1->akf.acc_x *= inv;
-    ac1->akf.acc_y *= inv;
-    ac1->akf.acc_z *= inv;
-    ac1->akf.acc_w *= inv;
+    // Normalize the data to the sample numbers.
+    kasaNormalize(&ac1->akf);
 
-    ac1->akf.acc_xx *= inv;
-    ac1->akf.acc_xy *= inv;
-    ac1->akf.acc_xz *= inv;
-    ac1->akf.acc_xw *= inv;
-
-    ac1->akf.acc_yy *= inv;
-    ac1->akf.acc_yz *= inv;
-    ac1->akf.acc_yw *= inv;
-
-    ac1->akf.acc_zz *= inv;
-    ac1->akf.acc_zw *= inv;
-
-    // Calculate the temp VAR and MEA.N
+    // Calculate the temp VAR and MEAN.
+    inv = 1.0f / ac1->akf.nsamples;
     ac1->agd.var_t =
         (ac1->agd.acc_tt - (ac1->agd.acc_t * ac1->agd.acc_t) * inv) * inv;
     ac1->agd.mean_t = ac1->agd.acc_t * inv;
@@ -395,7 +368,7 @@
       ac1->agd.ny > ac1->agd.nfy || ac1->agd.nyb > ac1->agd.nfyb ||
       ac1->agd.nz > ac1->agd.nfz || ac1->agd.nzb > ac1->agd.nfzb) {
     agdReset(&ac1->agd);
-    magKasaReset(&ac1->akf);
+    kasaReset(&ac1->akf);
     complete = 0;
     return complete;
   }
@@ -488,11 +461,11 @@
                   ", %s%d.%02d,  %llu, %s%d.%05d, %s%d.%05d, %s%d.%05d \n",
                   CAL_ENCODE_FLOAT(temp, 2),
                   (unsigned long long int)sample_time_nanos,
-                  CAL_ENCODE_FLOAT(acc->x_bias_new,5),
-                  CAL_ENCODE_FLOAT(acc->y_bias_new,5),
-                  CAL_ENCODE_FLOAT(acc->z_bias_new,5));
+                  CAL_ENCODE_FLOAT(acc->x_bias_new, 5),
+                  CAL_ENCODE_FLOAT(acc->y_bias_new, 5),
+                  CAL_ENCODE_FLOAT(acc->z_bias_new, 5));
     acc->temp_time_nanos = sample_time_nanos;
-    }
+  }
 #endif
 
   int temp_gate = 0;
@@ -523,7 +496,8 @@
         float radius;
 
         // Grabbing the fit from the MAG cal.
-        magKasaFit(&acc->ac1[temp_gate].akf, &bias, &radius);
+        kasaFit(&acc->ac1[temp_gate].akf, &bias, &radius, G_NORM_MAX,
+                G_NORM_MIN);
 
         // If offset is too large don't take.
         if (fabsf(bias.x) < MAX_OFF && fabsf(bias.y) < MAX_OFF &&
@@ -567,7 +541,7 @@
 
         // Resetting the structs for a new accel cal run.
         agdReset(&acc->ac1[temp_gate].agd);
-        magKasaReset(&acc->ac1[temp_gate].akf);
+        kasaReset(&acc->ac1[temp_gate].akf);
       }
     }
   }
diff --git a/firmware/os/algos/calibration/accelerometer/accel_cal.h b/firmware/os/algos/calibration/accelerometer/accel_cal.h
index 1cfef61..3324875 100644
--- a/firmware/os/algos/calibration/accelerometer/accel_cal.h
+++ b/firmware/os/algos/calibration/accelerometer/accel_cal.h
@@ -27,7 +27,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
-#include "calibration/magnetometer/mag_cal.h"
+#include "common/math/kasa.h"
 #include "common/math/mat.h"
 
 #ifdef __cplusplus
@@ -127,6 +127,25 @@
   struct KasaFit akf;
 };
 
+// AccelCal algorithm parameters (see the AccelCal for details).
+struct AccelCalParameters {
+  // t0  -> Sets the time how long the accel has to be still in ns.
+  // n_s -> Defines the minimum number of samples for the stillness.
+  // th  -> Sets the threshold for the stillness VAR in (g rms)^2.
+  // fx,fxb,fy,fyb,fz,fzb,fle -> Defines how many counts of data in the
+  //                             sphere cap (Bucket) is needed to reach full.
+  uint32_t t0;
+  uint32_t n_s;
+  uint32_t fx;
+  uint32_t fxb;
+  uint32_t fy;
+  uint32_t fyb;
+  uint32_t fz;
+  uint32_t fzb;
+  uint32_t fle;
+  float th;
+};
+
 // Complete accel calibration struct.
 struct AccelCal {
   struct AccelCalAlgo ac1[ACCEL_CAL_NUM_TEMP_WINDOWS];
@@ -163,15 +182,15 @@
                  float y, float z, float temp);
 
 /* This function initializes the accCalRun data struct.
- * t0     -> Sets the time how long the accel has to be still in ns.
- * n_s    -> Defines the minimum number of samples for the stillness.
- * th     -> Sets the threshold for the stillness VAR in (g rms)^2.
- * fx,fxb,fy,fyb,fz,fzb,fle -> Defines how many counts of data in the
- *                             sphere cap (Bucket) is needed to reach full.
+ * [parameters]:
+ *   t0     -> Sets the time how long the accel has to be still in ns.
+ *   n_s    -> Defines the minimum number of samples for the stillness.
+ *   th     -> Sets the threshold for the stillness VAR in (g rms)^2.
+ *   fx,fxb,fy,fyb,fz,fzb,fle -> Defines how many counts of data in the
+ *                               sphere cap (Bucket) is needed to reach full.
  */
-void accelCalInit(struct AccelCal *acc, uint32_t t0, uint32_t n_s, float th,
-                  uint32_t fx, uint32_t fxb, uint32_t fy, uint32_t fyb,
-                  uint32_t fz, uint32_t fzb, uint32_t fle);
+void accelCalInit(struct AccelCal *acc,
+                  const struct AccelCalParameters *parameters);
 
 void accelCalDestroy(struct AccelCal *acc);
 
@@ -182,6 +201,9 @@
 
 void accelCalBiasRemove(struct AccelCal *acc, float *x, float *y, float *z);
 
+// Returns true when a new accel calibration is available.
+bool accelCalNewBiasAvailable(struct AccelCal *acc);
+
 #ifdef ACCEL_CAL_DBG_ENABLED
 void accelCalDebPrint(struct AccelCal *acc, float temp);
 #endif
diff --git a/firmware/os/algos/calibration/common/calibration_data.c b/firmware/os/algos/calibration/common/calibration_data.c
deleted file mode 100644
index 9ae72d2..0000000
--- a/firmware/os/algos/calibration/common/calibration_data.c
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "calibration/common/calibration_data.h"
-
-#include <string.h>
-
-#include "common/math/vec.h"
-
-// FUNCTION IMPLEMENTATIONS
-//////////////////////////////////////////////////////////////////////////////
-
-// Set calibration data to identity scale factors, zero skew and
-// zero bias.
-void calDataReset(struct ThreeAxisCalData *calstruct) {
-  memset(calstruct, 0, sizeof(struct ThreeAxisCalData));
-  calstruct->scale_factor_x = 1.0f;
-  calstruct->scale_factor_y = 1.0f;
-  calstruct->scale_factor_z = 1.0f;
-}
-
-void calDataCorrectData(const struct ThreeAxisCalData* calstruct,
-                        const float x_impaired[THREE_AXIS_DIM],
-                        float* x_corrected) {
-  // x_temp = (x_impaired - bias).
-  float x_temp[THREE_AXIS_DIM];
-  vecSub(x_temp, x_impaired, calstruct->bias, THREE_AXIS_DIM);
-
-  // x_corrected = scale_skew_mat * x_temp, where:
-  // scale_skew_mat = [scale_factor_x    0         0
-  //                   skew_yx    scale_factor_y   0
-  //                   skew_zx       skew_zy   scale_factor_z].
-  x_corrected[0] = calstruct->scale_factor_x * x_temp[0];
-  x_corrected[1] = calstruct->skew_yx * x_temp[0] +
-      calstruct->scale_factor_y * x_temp[1];
-  x_corrected[2] = calstruct->skew_zx * x_temp[0] +
-      calstruct->skew_zy * x_temp[1] +
-      calstruct->scale_factor_z * x_temp[2];
-}
diff --git a/firmware/os/algos/calibration/common/calibration_data.h b/firmware/os/algos/calibration/common/calibration_data.h
deleted file mode 100644
index b0e6809..0000000
--- a/firmware/os/algos/calibration/common/calibration_data.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/////////////////////////////////////////////////////////////////////////////
-/*
- * This module contains a data structure and corresponding helper functions for
- * a three-axis sensor calibration.  The calibration consists of a bias vector,
- * bias, and a lower-diagonal scaling and skew matrix, scale_skew_mat.
- *
- * The calibration is applied to impaired sensor data as follows:
- *
- * corrected_data = scale_skew_mat * (impaired_data - bias).
- */
-#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_CALIBRATION_DATA_H_
-#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_CALIBRATION_DATA_H_
-
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define THREE_AXIS_DIM (3)
-
-// Calibration data structure.
-struct ThreeAxisCalData {
-  // Scale factor and skew terms.  Used to construct the following lower
-  // diagonal scale_skew_mat:
-  // scale_skew_mat = [scale_factor_x    0         0
-  //                   skew_yx    scale_factor_y   0
-  //                   skew_zx       skew_zy   scale_factor_z].
-  float scale_factor_x;
-  float scale_factor_y;
-  float scale_factor_z;
-  float skew_yx;
-  float skew_zx;
-  float skew_zy;
-
-  // Sensor bias offset.
-  float bias[THREE_AXIS_DIM];
-
-  // Calibration time.
-  uint64_t calibration_time_nanos;
-};
-
-// Set calibration data to identity scale factors, zero skew and
-// zero bias.
-void calDataReset(struct ThreeAxisCalData *calstruct);
-
-// Apply a stored calibration to correct a single sample of impaired sensor
-// data.
-void calDataCorrectData(const struct ThreeAxisCalData* calstruct,
-                        const float x_impaired[THREE_AXIS_DIM],
-                        float* x_corrected);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_CALIBRATION_DATA_H_
diff --git a/firmware/os/algos/calibration/common/sphere_fit_calibration.c b/firmware/os/algos/calibration/common/sphere_fit_calibration.c
deleted file mode 100644
index b417506..0000000
--- a/firmware/os/algos/calibration/common/sphere_fit_calibration.c
+++ /dev/null
@@ -1,247 +0,0 @@
-#include "calibration/common/sphere_fit_calibration.h"
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "calibration/util/cal_log.h"
-#include "common/math/mat.h"
-#include "common/math/vec.h"
-
-// FORWARD DECLARATIONS
-///////////////////////////////////////////////////////////////////////////////
-// Utility for converting solver state to a calibration data structure.
-static void convertStateToCalStruct(const float x[SF_STATE_DIM],
-                                    struct ThreeAxisCalData *calstruct);
-
-static bool runCalibration(struct SphereFitCal *sphere_cal,
-                           const struct SphereFitData *data,
-                           uint64_t timestamp_nanos);
-
-#define MIN_VALID_DATA_NORM (1e-4f)
-
-// FUNCTION IMPLEMENTATIONS
-//////////////////////////////////////////////////////////////////////////////
-void sphereFitInit(struct SphereFitCal *sphere_cal,
-                   const struct LmParams *lm_params,
-                   const size_t min_num_points_for_cal) {
-  ASSERT_NOT_NULL(sphere_cal);
-  ASSERT_NOT_NULL(lm_params);
-
-  // Initialize LM solver.
-  lmSolverInit(&sphere_cal->lm_solver, lm_params,
-               &sphereFitResidAndJacobianFunc);
-
-  // Reset other parameters.
-  sphereFitReset(sphere_cal);
-
-  // Set num points for calibration, checking that it is above min.
-  if (min_num_points_for_cal < MIN_NUM_SPHERE_FIT_POINTS) {
-    sphere_cal->min_points_for_cal = MIN_NUM_SPHERE_FIT_POINTS;
-  } else {
-    sphere_cal->min_points_for_cal = min_num_points_for_cal;
-  }
-}
-
-void sphereFitReset(struct SphereFitCal *sphere_cal) {
-  ASSERT_NOT_NULL(sphere_cal);
-
-  // Set state to default (diagonal scale matrix and zero offset).
-  memset(&sphere_cal->x0[0], 0, sizeof(float) * SF_STATE_DIM);
-  sphere_cal->x0[eParamScaleMatrix11] = 1.f;
-  sphere_cal->x0[eParamScaleMatrix22] = 1.f;
-  sphere_cal->x0[eParamScaleMatrix33] = 1.f;
-  memcpy(sphere_cal->x, sphere_cal->x0, sizeof(sphere_cal->x));
-
-  // Reset time.
-  sphere_cal->estimate_time_nanos = 0;
-}
-
-void sphereFitSetSolverData(struct SphereFitCal *sphere_cal,
-                            struct LmData *lm_data) {
-  ASSERT_NOT_NULL(sphere_cal);
-  ASSERT_NOT_NULL(lm_data);
-
-  // Set solver data.
-  lmSolverSetData(&sphere_cal->lm_solver, lm_data);
-}
-
-bool sphereFitRunCal(struct SphereFitCal *sphere_cal,
-                     const struct SphereFitData *data,
-                     uint64_t timestamp_nanos) {
-  ASSERT_NOT_NULL(sphere_cal);
-  ASSERT_NOT_NULL(data);
-
-  // Run calibration if have enough points.
-  if (data->num_fit_points >= sphere_cal->min_points_for_cal) {
-    return runCalibration(sphere_cal, data, timestamp_nanos);
-  }
-
-  return false;
-}
-
-void sphereFitSetInitialBias(struct SphereFitCal *sphere_cal,
-                             const float initial_bias[THREE_AXIS_DIM]) {
-  ASSERT_NOT_NULL(sphere_cal);
-  sphere_cal->x0[eParamOffset1] = initial_bias[0];
-  sphere_cal->x0[eParamOffset2] = initial_bias[1];
-  sphere_cal->x0[eParamOffset3] = initial_bias[2];
-}
-
-void sphereFitGetLatestCal(const struct SphereFitCal *sphere_cal,
-                           struct ThreeAxisCalData *cal_data) {
-  ASSERT_NOT_NULL(sphere_cal);
-  ASSERT_NOT_NULL(cal_data);
-  convertStateToCalStruct(sphere_cal->x, cal_data);
-  cal_data->calibration_time_nanos = sphere_cal->estimate_time_nanos;
-}
-
-void sphereFitResidAndJacobianFunc(const float *state, const void *f_data,
-                                   float *residual, float *jacobian) {
-  ASSERT_NOT_NULL(state);
-  ASSERT_NOT_NULL(f_data);
-  ASSERT_NOT_NULL(residual);
-
-  const struct SphereFitData *data = (const struct SphereFitData*)f_data;
-
-  // Verify that expected norm is non-zero, else use default of 1.0.
-  float expected_norm = 1.0;
-  ASSERT(data->expected_norm > MIN_VALID_DATA_NORM);
-  if (data->expected_norm > MIN_VALID_DATA_NORM) {
-    expected_norm = data->expected_norm;
-  }
-
-  // Convert parameters to calibration structure.
-  struct ThreeAxisCalData calstruct;
-  convertStateToCalStruct(state, &calstruct);
-
-  // Compute Jacobian helper matrix if Jacobian requested.
-  //
-  // J = d(||M(x_data - bias)|| - expected_norm)/dstate
-  //   = (M(x_data - bias) / ||M(x_data - bias)||) * d(M(x_data - bias))/dstate
-  //   = x_corr / ||x_corr|| * A
-  // A = d(M(x_data - bias))/dstate
-  //   = [dy/dM11, dy/dM21, dy/dM22, dy/dM31, dy/dM32, dy/dM3,...
-  //      dy/db1, dy/db2, dy/db3]'
-  // where:
-  // dy/dM11 = [x_data[0] - bias[0], 0, 0]
-  // dy/dM21 = [0, x_data[0] - bias[0], 0]
-  // dy/dM22 = [0, x_data[1] - bias[1], 0]
-  // dy/dM31 = [0, 0, x_data[0] - bias[0]]
-  // dy/dM32 = [0, 0, x_data[1] - bias[1]]
-  // dy/dM33 = [0, 0, x_data[2] - bias[2]]
-  // dy/db1 = [-scale_factor_x, 0, 0]
-  // dy/db2 = [0, -scale_factor_y, 0]
-  // dy/db3 = [0, 0, -scale_factor_z]
-  float A[SF_STATE_DIM * THREE_AXIS_DIM];
-  if (jacobian) {
-    memset(jacobian, 0, sizeof(float) * SF_STATE_DIM * data->num_fit_points);
-    memset(A, 0, sizeof(A));
-    A[0 * SF_STATE_DIM + eParamOffset1] = -calstruct.scale_factor_x;
-    A[1 * SF_STATE_DIM + eParamOffset2] = -calstruct.scale_factor_y;
-    A[2 * SF_STATE_DIM + eParamOffset3] = -calstruct.scale_factor_z;
-  }
-
-  // Loop over all data points to compute residual and Jacobian.
-  // TODO(dvitus): Use fit_data_std when available to weight residuals.
-  float x_corr[THREE_AXIS_DIM];
-  float x_bias_corr[THREE_AXIS_DIM];
-  size_t i;
-  for (i = 0; i < data->num_fit_points; ++i) {
-    const float *x_data = &data->fit_data[i * THREE_AXIS_DIM];
-
-    // Compute corrected sensor data
-    calDataCorrectData(&calstruct, x_data, x_corr);
-
-    // Compute norm of x_corr.
-    const float norm = vecNorm(x_corr, THREE_AXIS_DIM);
-
-    // Compute residual error: f_x = norm - exp_norm
-    residual[i] = norm - data->expected_norm;
-
-    // Compute Jacobian if valid pointer.
-    if (jacobian) {
-      if (norm < MIN_VALID_DATA_NORM) {
-        return;
-      }
-      const float scale = 1.f / norm;
-
-      // Compute bias corrected data.
-      vecSub(x_bias_corr, x_data, calstruct.bias, THREE_AXIS_DIM);
-
-      // Populate non-bias terms for A
-      A[0 + eParamScaleMatrix11] = x_bias_corr[0];
-      A[1 * SF_STATE_DIM + eParamScaleMatrix21] = x_bias_corr[0];
-      A[1 * SF_STATE_DIM + eParamScaleMatrix22] = x_bias_corr[1];
-      A[2 * SF_STATE_DIM + eParamScaleMatrix31] = x_bias_corr[0];
-      A[2 * SF_STATE_DIM + eParamScaleMatrix32] = x_bias_corr[1];
-      A[2 * SF_STATE_DIM + eParamScaleMatrix33] = x_bias_corr[2];
-
-      // Compute J = x_corr / ||x_corr|| * A
-      matTransposeMultiplyVec(&jacobian[i * SF_STATE_DIM], A, x_corr,
-                              THREE_AXIS_DIM, SF_STATE_DIM);
-      vecScalarMulInPlace(&jacobian[i * SF_STATE_DIM], scale, SF_STATE_DIM);
-    }
-  }
-}
-
-void convertStateToCalStruct(const float x[SF_STATE_DIM],
-                             struct ThreeAxisCalData *calstruct) {
-  memcpy(&calstruct->bias[0], &x[eParamOffset1],
-         sizeof(float) * THREE_AXIS_DIM);
-  calstruct->scale_factor_x = x[eParamScaleMatrix11];
-  calstruct->skew_yx = x[eParamScaleMatrix21];
-  calstruct->scale_factor_y = x[eParamScaleMatrix22];
-  calstruct->skew_zx = x[eParamScaleMatrix31];
-  calstruct->skew_zy = x[eParamScaleMatrix32];
-  calstruct->scale_factor_z = x[eParamScaleMatrix33];
-}
-
-bool runCalibration(struct SphereFitCal *sphere_cal,
-                    const struct SphereFitData *data,
-                    uint64_t timestamp_nanos) {
-  float x_sol[SF_STATE_DIM];
-
-  // Run calibration
-  const enum LmStatus status = lmSolverSolve(&sphere_cal->lm_solver,
-                                             sphere_cal->x0, (void *)data,
-                                             SF_STATE_DIM, data->num_fit_points,
-                                             x_sol);
-
-  // Check if solver was successful
-  if (status == RELATIVE_STEP_SUFFICIENTLY_SMALL ||
-      status == GRADIENT_SUFFICIENTLY_SMALL) {
-    // TODO(dvitus): Check quality of new fit before using.
-    // Store new fit.
-#ifdef SPHERE_FIT_DBG_ENABLED
-    CAL_DEBUG_LOG(
-        "[SPHERE CAL]",
-        "Solution found in %d iterations with status %d.\n",
-        sphere_cal->lm_solver.num_iter, status);
-    CAL_DEBUG_LOG(
-        "[SPHERE CAL]",
-        "Solution:\n {%s%d.%06d [M(1,1)], %s%d.%06d [M(2,1)], "
-        "%s%d.%06d [M(2,2)], \n"
-        "%s%d.%06d [M(3,1)], %s%d.%06d [M(3,2)], %s%d.%06d [M(3,3)], \n"
-        "%s%d.%06d [b(1)], %s%d.%06d [b(2)], %s%d.%06d [b(3)]}.",
-        CAL_ENCODE_FLOAT(x_sol[0], 6), CAL_ENCODE_FLOAT(x_sol[1], 6),
-        CAL_ENCODE_FLOAT(x_sol[2], 6), CAL_ENCODE_FLOAT(x_sol[3], 6),
-        CAL_ENCODE_FLOAT(x_sol[4], 6), CAL_ENCODE_FLOAT(x_sol[5], 6),
-        CAL_ENCODE_FLOAT(x_sol[6], 6), CAL_ENCODE_FLOAT(x_sol[7], 6),
-        CAL_ENCODE_FLOAT(x_sol[8], 6));
-#endif
-    memcpy(sphere_cal->x, x_sol, sizeof(x_sol));
-    sphere_cal->estimate_time_nanos = timestamp_nanos;
-    return true;
-  } else {
-#ifdef SPHERE_FIT_DBG_ENABLED
-     CAL_DEBUG_LOG(
-        "[SPHERE CAL]",
-        "Solution failed in %d iterations with status %d.\n",
-        sphere_cal->lm_solver.num_iter, status);
-#endif
-  }
-
-  return false;
-}
diff --git a/firmware/os/algos/calibration/common/sphere_fit_calibration.h b/firmware/os/algos/calibration/common/sphere_fit_calibration.h
deleted file mode 100644
index e49f225..0000000
--- a/firmware/os/algos/calibration/common/sphere_fit_calibration.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/////////////////////////////////////////////////////////////////////////////
-/*
- * This module contains an algorithm for performing a sphere fit calibration.
- * A sphere fit calibration solves the following non-linear least squares
- * problem:
- *
- *   arg min || ||M(x - b)|| - exp_norm ||
- *      M,b
- *
- * where:
- *  x is a 3xN matrix containing N 3-dimensional uncalibrated data points,
- *  M is a 3x3 lower diagonal scaling matrix
- *  b is a 3x1 offset vector.
- *  exp_norm is the expected norm of an individual calibration data point.
- * M and b are solved such that the norm of the calibrated data (M(x - b)) is
- * near exp_norm.
- *
- * This module uses a Levenberg-Marquardt nonlinear least squares solver to find
- * M and b.  M is assumed to be a lower diagonal, consisting of 6 parameters.
- *
- */
-#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_SPHERE_FIT_CALIBRATION_H_
-#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_SPHERE_FIT_CALIBRATION_H_
-
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "calibration/common/calibration_data.h"
-#include "common/math/levenberg_marquardt.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define MIN_NUM_SPHERE_FIT_POINTS (14)
-
-// Enum defining the meaning of the state parameters.  The 9-parameter
-// sphere fit calibration computes a lower-diagonal scaling matrix (M) and
-// an offset such that:
-//    x_corrected = M * (x_impaired - offset)
-enum SphereFitParams {
-  eParamScaleMatrix11 = 0,
-  eParamScaleMatrix21,
-  eParamScaleMatrix22,
-  eParamScaleMatrix31,
-  eParamScaleMatrix32,
-  eParamScaleMatrix33,
-  eParamOffset1,
-  eParamOffset2,
-  eParamOffset3,
-  SF_STATE_DIM
-};
-
-// Structure containing the data to be used for the sphere fit calibration.
-struct SphereFitData {
-  // Data for fit (assumed to be a matrix of size num_fit_points x SF_DATA_DIM)
-  const float *fit_data;
-
-  // Pointer to standard deviations of the fit data, used to weight individual
-  // data points.  Assumed to point to a matrix of dimensions
-  // num_fit_points x THREE_AXIS_DIM.
-  // If NULL, data will all be used with equal weighting in the fit.
-  const float *fit_data_std;
-
-  // Number of fit points.
-  size_t num_fit_points;
-
-  // Expected data norm.
-  float expected_norm;
-};
-
-// Structure for a sphere fit calibration, including a non-linear least squares
-// solver and the latest state estimate.
-struct SphereFitCal {
-  // Levenberg-Marquardt solver.
-  struct LmSolver lm_solver;
-
-  // Minimum number of points for computing a calibration.
-  size_t min_points_for_cal;
-
-  // State estimate.
-  float x[SF_STATE_DIM];
-  uint64_t estimate_time_nanos;
-
-  // Initial state for solver.
-  float x0[SF_STATE_DIM];
-};
-
-// Initialize sphere fit calibration structure with solver and fit params.
-void sphereFitInit(struct SphereFitCal *sphere_cal,
-                   const struct LmParams *lm_params,
-                   const size_t min_num_points_for_cal);
-
-// Clears state estimate and initial state.
-void sphereFitReset(struct SphereFitCal *sphere_cal);
-
-// Sets data pointer for single solve of the Levenberg-Marquardt solver.
-// Must be called before calling sphereFitRunCal().
-void sphereFitSetSolverData(struct SphereFitCal *sphere_cal,
-                            struct LmData *lm_data);
-
-// Sends in a set of calibration data and attempts to run calibration.
-// Returns true if a calibration was successfully triggered with this data.
-bool sphereFitRunCal(struct SphereFitCal *sphere_cal,
-                     const struct SphereFitData *data,
-                     uint64_t timestamp_nanos);
-
-// Set an initial condition for the bias state.
-void sphereFitSetInitialBias(struct SphereFitCal *sphere_cal,
-                             const float initial_bias[THREE_AXIS_DIM]);
-
-// Returns the latest calibration data in a ThreeAxisCalData structure.
-void sphereFitGetLatestCal(const struct SphereFitCal *sphere_cal,
-                           struct ThreeAxisCalData *cal_data);
-
-/////////////////  TEST UTILITIES ///////////////////////////////////////////
-// The following functions are exposed in the header for testing only.
-
-// The ResidualAndJacobianFunction for sphere calibration in the
-// Levenberg-Marquardt solver.
-void sphereFitResidAndJacobianFunc(const float *state, const void *f_data,
-                                   float *residual, float *jacobian);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  //  LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_SPHERE_FIT_CALIBRATION_H_
diff --git a/firmware/os/algos/calibration/common/diversity_checker.c b/firmware/os/algos/calibration/diversity_checker/diversity_checker.c
similarity index 68%
rename from firmware/os/algos/calibration/common/diversity_checker.c
rename to firmware/os/algos/calibration/diversity_checker/diversity_checker.c
index 3655147..3fab81f 100644
--- a/firmware/os/algos/calibration/common/diversity_checker.c
+++ b/firmware/os/algos/calibration/diversity_checker/diversity_checker.c
@@ -14,52 +14,46 @@
  * limitations under the License.
  */
 
-#include "calibration/common/diversity_checker.h"
+#include "calibration/diversity_checker/diversity_checker.h"
 
 #include <errno.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
+
 #include "common/math/vec.h"
 
 // Struct initialization.
-void diversityCheckerInit(
-    struct DiversityChecker* diverse_data,
-    size_t min_num_diverse_vectors,
-    size_t max_num_max_distance,
-    float var_threshold,
-    float max_min_threshold,
-    float local_field,
-    float threshold_tuning_param,
-    float max_distance_tuning_param) {
+void diversityCheckerInit(struct DiversityChecker* diverse_data,
+                          const struct DiversityCheckerParameters* parameters) {
   ASSERT_NOT_NULL(diverse_data);
 
   // Initialize parameters.
   diverse_data->threshold_tuning_param_sq =
-      (threshold_tuning_param * threshold_tuning_param);
+      (parameters->threshold_tuning_param * parameters->threshold_tuning_param);
   diverse_data->max_distance_tuning_param_sq =
-      (max_distance_tuning_param * max_distance_tuning_param);
+      (parameters->max_distance_tuning_param *
+       parameters->max_distance_tuning_param);
 
   // Updating the threshold and max_distance using assumed local field.
   // Testing for zero and negative local_field.
-  if (local_field <= 0) {
-    local_field = 1;
-  }
+  const float local_field =
+      (parameters->local_field <= 0.0f) ? 1.0f : parameters->local_field;
   diversityCheckerLocalFieldUpdate(diverse_data, local_field);
-  diverse_data->min_num_diverse_vectors = min_num_diverse_vectors;
+  diverse_data->min_num_diverse_vectors = parameters->min_num_diverse_vectors;
 
   // Checking for min_num_diverse_vectors = 0.
-  if (min_num_diverse_vectors < 1) {
+  if (parameters->min_num_diverse_vectors < 1) {
     diverse_data->min_num_diverse_vectors = 1;
   }
-  diverse_data->max_num_max_distance = max_num_max_distance;
-  diverse_data->var_threshold = var_threshold;
-  diverse_data->max_min_threshold = max_min_threshold;
+  diverse_data->max_num_max_distance = parameters->max_num_max_distance;
+  diverse_data->var_threshold = parameters->var_threshold;
+  diverse_data->max_min_threshold = parameters->max_min_threshold;
 
   // Setting the rest to zero.
   diversityCheckerReset(diverse_data);
 
-   // Debug Messages
+  // Debug Messages
 #ifdef DIVERSE_DEBUG_ENABLE
   memset(&diverse_data->diversity_dbg, 0, sizeof(diverse_data->diversity_dbg));
 #endif
@@ -69,8 +63,7 @@
 void diversityCheckerReset(struct DiversityChecker* diverse_data) {
   ASSERT_NOT_NULL(diverse_data);
   // Clear data memory.
-  memset(&diverse_data->diverse_data, 0,
-         sizeof(diverse_data->diverse_data));
+  memset(&diverse_data->diverse_data, 0, sizeof(diverse_data->diverse_data));
 
   // Resetting counters and data full bit.
   diverse_data->num_points = 0;
@@ -78,13 +71,13 @@
   diverse_data->data_full = false;
 }
 
-int32_t diversityCheckerFindNearestPoint(struct DiversityChecker* diverse_data,
-                                         float x, float y, float z) {
+bool diversityCheckerFindNearestPoint(struct DiversityChecker* diverse_data,
+                                      float x, float y, float z) {
   // Converting three single inputs to a vector.
-  const float vec[3] = {x, y, z};
+  const float vec[THREE_AXIS_DATA_DIM] = {x, y, z};
 
   // Result vector for vector difference.
-  float vec_diff[3];
+  float vec_diff[THREE_AXIS_DATA_DIM];
 
   // normSquared result (k)
   float norm_squared_result;
@@ -94,9 +87,7 @@
   // Running over all existing data points
   for (i = 0; i < diverse_data->num_points; ++i) {
     // v = v1 - v2;
-    vecSub(vec_diff,
-           &diverse_data->diverse_data[i * THREE_AXIS_DATA_DIM],
-           vec,
+    vecSub(vec_diff, &diverse_data->diverse_data[i * THREE_AXIS_DATA_DIM], vec,
            THREE_AXIS_DATA_DIM);
 
     // k = |v|^2
@@ -104,41 +95,40 @@
 
     // if k < Threshold then leave the function.
     if (norm_squared_result < diverse_data->threshold) {
-      return (int32_t)i;
+      return false;
     }
 
     // if k > max_distance, count and leave the function.
     if (norm_squared_result > diverse_data->max_distance) {
       diverse_data->num_max_dist_violations++;
-      return -2;
+      return false;
     }
   }
-  return -1;
+  return true;
 }
 
-void diversityCheckerUpdate(
-    struct DiversityChecker* diverse_data, float x, float y, float z) {
+void diversityCheckerUpdate(struct DiversityChecker* diverse_data, float x,
+                            float y, float z) {
   ASSERT_NOT_NULL(diverse_data);
 
   // If memory is full, no need to run through the data.
   if (!diverse_data->data_full) {
-
-    // diversityCheckerDataSet() returns -1, if input data is diverse against
+    // diversityCheckerDataSet() returns true, if input data is diverse against
     // the already stored.
-    if (diversityCheckerFindNearestPoint(diverse_data, x, y, z) == -1) {
+    if (diversityCheckerFindNearestPoint(diverse_data, x, y, z)) {
       // Converting three single inputs to a vector.
-      const float vec[3] = {x, y, z};
+      const float vec[THREE_AXIS_DATA_DIM] = {x, y, z};
 
       // Notice that the first data vector will be stored no matter what.
-      memcpy(&diverse_data->
-             diverse_data[diverse_data->num_points * THREE_AXIS_DATA_DIM],
-             vec,
-             sizeof(float) * THREE_AXIS_DATA_DIM);
+      memcpy(
+          &diverse_data
+               ->diverse_data[diverse_data->num_points * THREE_AXIS_DATA_DIM],
+          vec, sizeof(float) * THREE_AXIS_DATA_DIM);
 
       // Count new data point.
       diverse_data->num_points++;
 
-      // Setting data_full to 1, if memory is full.
+      // Setting data_full to true, if memory is full.
       if (diverse_data->num_points == NUM_DIVERSE_VECTORS) {
         diverse_data->data_full = true;
       }
@@ -147,18 +137,16 @@
 }
 
 bool diversityCheckerNormQuality(struct DiversityChecker* diverse_data,
-                                 float x_bias,
-                                 float y_bias,
-                                 float z_bias) {
+                                 float x_bias, float y_bias, float z_bias) {
   ASSERT_NOT_NULL(diverse_data);
   // If not enough diverse data points or max distance violations return false.
   if (diverse_data->num_points <= diverse_data->min_num_diverse_vectors ||
       diverse_data->num_max_dist_violations >=
-      diverse_data->max_num_max_distance) {
+          diverse_data->max_num_max_distance) {
     return false;
   }
-  float vec_bias[3] = {x_bias, y_bias, z_bias};
-  float vec_bias_removed[3];
+  float vec_bias[THREE_AXIS_DATA_DIM] = {x_bias, y_bias, z_bias};
+  float vec_bias_removed[THREE_AXIS_DATA_DIM];
   float norm_results;
   float acc_norm = 0.0f;
   float acc_norm_square = 0.0f;
@@ -168,8 +156,7 @@
   for (i = 0; i < diverse_data->num_points; ++i) {
     // v = v1 - v_bias;
     vecSub(vec_bias_removed,
-           &diverse_data->diverse_data[i * THREE_AXIS_DATA_DIM],
-           vec_bias,
+           &diverse_data->diverse_data[i * THREE_AXIS_DATA_DIM], vec_bias,
            THREE_AXIS_DATA_DIM);
 
     // norm = ||v||
@@ -177,7 +164,7 @@
 
     // Accumulate for mean and VAR.
     acc_norm += norm_results;
-    acc_norm_square += norm_results * norm_results ;
+    acc_norm_square += norm_results * norm_results;
 
     if (i == 0) {
       min = norm_results;
@@ -219,11 +206,11 @@
                                       float local_field) {
   if (local_field > 0) {
     // Updating threshold based on the local field information.
-    diverse_data->threshold = diverse_data->threshold_tuning_param_sq *
-        (local_field * local_field);
+    diverse_data->threshold =
+        diverse_data->threshold_tuning_param_sq * (local_field * local_field);
 
     // Updating max distance based on the local field information.
     diverse_data->max_distance = diverse_data->max_distance_tuning_param_sq *
-        (local_field * local_field);
+                                 (local_field * local_field);
   }
 }
diff --git a/firmware/os/algos/calibration/common/diversity_checker.h b/firmware/os/algos/calibration/diversity_checker/diversity_checker.h
similarity index 84%
rename from firmware/os/algos/calibration/common/diversity_checker.h
rename to firmware/os/algos/calibration/diversity_checker/diversity_checker.h
index 62a1b3e..fe95196 100644
--- a/firmware/os/algos/calibration/common/diversity_checker.h
+++ b/firmware/os/algos/calibration/diversity_checker/diversity_checker.h
@@ -41,8 +41,8 @@
  * full. This has been done in order to save processing power.
  */
 
-#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_DIVERSITY_CHECKER_H_
-#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_DIVERSITY_CHECKER_H_
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_DIVERSITY_CHECKER_DIVERSITY_CHECKER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_DIVERSITY_CHECKER_DIVERSITY_CHECKER_H_
 
 #include <stdbool.h>
 #include <stddef.h>
@@ -68,6 +68,17 @@
 };
 #endif
 
+// DiversityChecker parameters container.
+struct DiversityCheckerParameters {
+  float var_threshold;
+  float max_min_threshold;
+  float local_field;
+  float threshold_tuning_param;
+  float max_distance_tuning_param;
+  size_t min_num_diverse_vectors;
+  size_t max_num_max_distance;
+};
+
 // Main data struct.
 struct DiversityChecker {
   // Data memory.
@@ -97,7 +108,6 @@
   bool data_full;
 
   // Setup variables for NormQuality check.
-
   size_t min_num_diverse_vectors;
   size_t max_num_max_distance;
   float var_threshold;
@@ -109,7 +119,7 @@
 #endif
 };
 
-// Initialization of the function/struct, input:
+// Initialization of the function/struct, input parameters struct consists of:
 // min_num_diverse_vectors -> sets the gate for a minimum number of data points
 //                           in the memory
 // max_num_max_distance -> sets the value for a max distance violation number
@@ -123,22 +133,17 @@
 // max_distance_tuning_param -> Max distance tuning parameter used to calculate
 //                             max_distance.
 void diversityCheckerInit(struct DiversityChecker* diverse_data,
-                          size_t min_num_diverse_vectors,
-                          size_t max_num_max_distance, float var_threshold,
-                          float max_min_threshold, float local_field,
-                          float threshold_tuning_param,
-                          float max_distance_tuning_param);
+                          const struct DiversityCheckerParameters* parameters);
 
 // Resetting the memory and the counters, leaves threshold and max_distance
 // as well as the setup variables for NormQuality check untouched.
 void diversityCheckerReset(struct DiversityChecker* diverse_data);
 
-// Checks if data point (x, y, z) is diverse against the diverse_data set. It
-// returns -1: when the input point is diverse or the index to which vector the
-// input is diverse
-  // returns -2: when a maximum distance check is violated
-int32_t diversityCheckerFindNearestPoint(struct DiversityChecker* diverse_data,
-                                         float x, float y, float z);
+// Checks if data point (x, y, z) is diverse against the diverse_data set.
+// Returns true when the input point is diverse.
+// Returns false when a maximum distance check is violated.
+bool diversityCheckerFindNearestPoint(struct DiversityChecker* diverse_data,
+                                      float x, float y, float z);
 
 // Main function. Tests the data (x,y,z) against the memory if diverse and
 // stores it, if so.
@@ -156,9 +161,7 @@
 // -> norm must be within a MAX/MIN window.
 // Returned value will only be true if all 4 gates are passed.
 bool diversityCheckerNormQuality(struct DiversityChecker* diverse_data,
-                                 float x_bias,
-                                 float y_bias,
-                                 float z_bias);
+                                 float x_bias, float y_bias, float z_bias);
 
 // This function updates the threshold value and max distance value based on the
 // local field. This ensures a local field independent operation of the
@@ -172,4 +175,4 @@
 }
 #endif
 
-#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_COMMON_DIVERSITY_CHECKER_H_
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_DIVERSITY_CHECKER_DIVERSITY_CHECKER_H_
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_cal.c b/firmware/os/algos/calibration/gyroscope/gyro_cal.c
index 3179b0e..9da7aa4 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_cal.c
+++ b/firmware/os/algos/calibration/gyroscope/gyro_cal.c
@@ -40,6 +40,10 @@
 // A debug version label to help with tracking results.
 #define GYROCAL_DEBUG_VERSION_STRING "[July 05, 2017]"
 
+// Parameters used for sample rate estimation.
+#define GYROCAL_DEBUG_SAMPLE_RATE_NUM_INTERVALS (100)
+#define GYROCAL_DEBUG_SAMPLE_RATE_GAP_SEC (1.0f)
+
 // Debug log tag string used to identify debug report output data.
 #define GYROCAL_REPORT_TAG "[GYRO_CAL:REPORT]"
 #endif  // GYRO_CAL_DBG_ENABLED
@@ -103,26 +107,6 @@
   MAG_STATS_TUNING
 };
 
-/*
- * Updates the running calculation of the gyro's mean sampling rate.
- *
- * Behavior:
- *   1)  If 'debug_mean_sampling_rate_hz' pointer is not NULL then the local
- *       calculation of the sampling rate is copied, and the function returns.
- *   2)  Else, if 'reset_stats' is 'true' then the local estimate is reset and
- *       the function returns.
- *   3)  Otherwise, the local estimate of the mean sampling rates is updated.
- *
- * INPUTS:
- *   sample_rate_estimator:  Pointer to the estimator data structure.
- *   debug_mean_sampling_rate_hz:  Pointer to the mean sampling rate to update.
- *   timestamp_nanos:  Time stamp (nanoseconds).
- *   reset_stats:  Flag that signals a reset of the sampling rate estimate.
- */
-static void gyroSamplingRateUpdate(struct SampleRateData* sample_rate_estimator,
-                                   float* debug_mean_sampling_rate_hz,
-                                   uint64_t timestamp_nanos, bool reset_stats);
-
 // Updates the information used for debug printouts.
 static void gyroCalUpdateDebug(struct GyroCal* gyro_cal);
 
@@ -130,35 +114,13 @@
 static void gyroCalDebugPrintData(const struct GyroCal* gyro_cal,
                                   char* debug_tag,
                                   enum DebugPrintData print_data);
-
-// This conversion function is necessary for Nanohub firmware compilation (i.e.,
-// can't cast a uint64_t to a float directly). This conversion function was
-// copied from: /third_party/contexthub/firmware/src/floatRt.c
-static float floatFromUint64(uint64_t v)
-{
-    uint32_t hi = v >> 32, lo = v;
-
-    if (!hi) //this is very fast for cases where we fit into a uint32_t
-        return (float)lo;
-    else {
-        return ((float)hi) * 4294967296.0f + (float)lo;
-    }
-}
 #endif  // GYRO_CAL_DBG_ENABLED
 
 /////// FUNCTION DEFINITIONS /////////////////////////////////////////
 
 // Initialize the gyro calibration data structure.
-void gyroCalInit(struct GyroCal* gyro_cal, uint64_t min_still_duration_nanos,
-                 uint64_t max_still_duration_nanos, float bias_x, float bias_y,
-                 float bias_z, uint64_t calibration_time_nanos,
-                 uint64_t window_time_duration_nanos, float gyro_var_threshold,
-                 float gyro_confidence_delta, float accel_var_threshold,
-                 float accel_confidence_delta, float mag_var_threshold,
-                 float mag_confidence_delta, float stillness_threshold,
-                 float stillness_mean_delta_limit,
-                 float temperature_delta_limit_celsius,
-                 bool gyro_calibration_enable) {
+void gyroCalInit(struct GyroCal* gyro_cal,
+                 const struct GyroCalParameters* parameters) {
   // Clear gyro_cal structure memory.
   memset(gyro_cal, 0, sizeof(struct GyroCal));
 
@@ -166,35 +128,38 @@
   // Gyro parameter input units are [rad/sec].
   // Accel parameter input units are [m/sec^2].
   // Magnetometer parameter input units are [uT].
-  gyroStillDetInit(&gyro_cal->gyro_stillness_detect, gyro_var_threshold,
-                   gyro_confidence_delta);
-  gyroStillDetInit(&gyro_cal->accel_stillness_detect, accel_var_threshold,
-                   accel_confidence_delta);
-  gyroStillDetInit(&gyro_cal->mag_stillness_detect, mag_var_threshold,
-                   mag_confidence_delta);
+  gyroStillDetInit(&gyro_cal->gyro_stillness_detect,
+                   parameters->gyro_var_threshold,
+                   parameters->gyro_confidence_delta);
+  gyroStillDetInit(&gyro_cal->accel_stillness_detect,
+                   parameters->accel_var_threshold,
+                   parameters->accel_confidence_delta);
+  gyroStillDetInit(&gyro_cal->mag_stillness_detect,
+                   parameters->mag_var_threshold,
+                   parameters->mag_confidence_delta);
 
   // Reset stillness flag and start timestamp.
   gyro_cal->prev_still = false;
   gyro_cal->start_still_time_nanos = 0;
 
   // Set the min and max window stillness duration.
-  gyro_cal->min_still_duration_nanos = min_still_duration_nanos;
-  gyro_cal->max_still_duration_nanos = max_still_duration_nanos;
+  gyro_cal->min_still_duration_nanos = parameters->min_still_duration_nanos;
+  gyro_cal->max_still_duration_nanos = parameters->max_still_duration_nanos;
 
   // Sets the duration of the stillness processing windows.
-  gyro_cal->window_time_duration_nanos = window_time_duration_nanos;
+  gyro_cal->window_time_duration_nanos = parameters->window_time_duration_nanos;
 
   // Set the watchdog timeout duration.
   gyro_cal->gyro_watchdog_timeout_duration_nanos = GYRO_WATCHDOG_TIMEOUT_NANOS;
 
   // Load the last valid cal from system memory.
-  gyro_cal->bias_x = bias_x;  // [rad/sec]
-  gyro_cal->bias_y = bias_y;  // [rad/sec]
-  gyro_cal->bias_z = bias_z;  // [rad/sec]
-  gyro_cal->calibration_time_nanos = calibration_time_nanos;
+  gyro_cal->bias_x = parameters->bias_x;  // [rad/sec]
+  gyro_cal->bias_y = parameters->bias_y;  // [rad/sec]
+  gyro_cal->bias_z = parameters->bias_z;  // [rad/sec]
+  gyro_cal->calibration_time_nanos = parameters->calibration_time_nanos;
 
   // Set the stillness threshold required for gyro bias calibration.
-  gyro_cal->stillness_threshold = stillness_threshold;
+  gyro_cal->stillness_threshold = parameters->stillness_threshold;
 
   // Current window end-time used to assist in keeping sensor data collection in
   // sync. Setting this to zero signals that sensor data will be dropped until a
@@ -202,13 +167,14 @@
   gyro_cal->stillness_win_endtime_nanos = 0;
 
   // Gyro calibrations will be applied (see, gyroCalRemoveBias()).
-  gyro_cal->gyro_calibration_enable = (gyro_calibration_enable > 0);
+  gyro_cal->gyro_calibration_enable = (parameters->gyro_calibration_enable > 0);
 
   // Sets the stability limit for the stillness window mean acceptable delta.
-  gyro_cal->stillness_mean_delta_limit = stillness_mean_delta_limit;
+  gyro_cal->stillness_mean_delta_limit = parameters->stillness_mean_delta_limit;
 
   // Sets the min/max temperature delta limit for the stillness period.
-  gyro_cal->temperature_delta_limit_celsius = temperature_delta_limit_celsius;
+  gyro_cal->temperature_delta_limit_celsius =
+      parameters->temperature_delta_limit_celsius;
 
   // Ensures that the data tracking functionality is reset.
   gyroStillMeanTracker(gyro_cal, DO_RESET);
@@ -221,41 +187,47 @@
     CAL_DEBUG_LOG("[GYRO_CAL:INIT]", "Online gyroscope calibration DISABLED.");
   }
 
-  // Ensures that the gyro sampling rate estimate is reset.
-  gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator, NULL, 0,
-                         /*reset_stats=*/true);
+  // Initializes the gyro sampling rate estimator.
+  sampleRateEstimatorInit(&gyro_cal->debug_gyro_cal.sample_rate_estimator,
+                          GYROCAL_DEBUG_SAMPLE_RATE_NUM_INTERVALS,
+                          GYROCAL_DEBUG_SAMPLE_RATE_GAP_SEC);
 #endif  // GYRO_CAL_DBG_ENABLED
 }
 
 // Void pointer in the gyro calibration data structure (doesn't do anything
 // except prevent compiler warnings).
-void gyroCalDestroy(struct GyroCal* gyro_cal) {
-  (void)gyro_cal;
-}
+void gyroCalDestroy(struct GyroCal* gyro_cal) { (void)gyro_cal; }
 
 // Get the most recent bias calibration value.
 void gyroCalGetBias(struct GyroCal* gyro_cal, float* bias_x, float* bias_y,
-                    float* bias_z, float* temperature_celsius) {
+                    float* bias_z, float* temperature_celsius,
+                    uint64_t* calibration_time_nanos) {
   *bias_x = gyro_cal->bias_x;
   *bias_y = gyro_cal->bias_y;
   *bias_z = gyro_cal->bias_z;
+  *calibration_time_nanos = gyro_cal->calibration_time_nanos;
   *temperature_celsius = gyro_cal->bias_temperature_celsius;
 }
 
 // Set an initial bias calibration value.
 void gyroCalSetBias(struct GyroCal* gyro_cal, float bias_x, float bias_y,
-                    float bias_z, uint64_t calibration_time_nanos) {
+                    float bias_z, float temperature_celsius,
+                    uint64_t calibration_time_nanos) {
   gyro_cal->bias_x = bias_x;
   gyro_cal->bias_y = bias_y;
   gyro_cal->bias_z = bias_z;
   gyro_cal->calibration_time_nanos = calibration_time_nanos;
+  gyro_cal->bias_temperature_celsius = temperature_celsius;
 
 #ifdef GYRO_CAL_DBG_ENABLED
   CAL_DEBUG_LOG("[GYRO_CAL:SET BIAS]",
-                "Gyro Bias Calibration [mDPS]: " CAL_FORMAT_3DIGITS_TRIPLET,
-                CAL_ENCODE_FLOAT(gyro_cal->bias_x * RAD_TO_MDEG, 3),
-                CAL_ENCODE_FLOAT(gyro_cal->bias_y * RAD_TO_MDEG, 3),
-                CAL_ENCODE_FLOAT(gyro_cal->bias_z * RAD_TO_MDEG, 3));
+                "Offset|Temp|Time [mDPS|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
+                ", " CAL_FORMAT_3DIGITS ", %llu",
+                CAL_ENCODE_FLOAT(bias_x * RAD_TO_MDEG, 3),
+                CAL_ENCODE_FLOAT(bias_y * RAD_TO_MDEG, 3),
+                CAL_ENCODE_FLOAT(bias_z * RAD_TO_MDEG, 3),
+                CAL_ENCODE_FLOAT(temperature_celsius, 3),
+                (unsigned long long int)calibration_time_nanos);
 #endif  // GYRO_CAL_DBG_ENABLED
 }
 
@@ -298,8 +270,8 @@
 
 #ifdef GYRO_CAL_DBG_ENABLED
   // Update the gyro sampling rate estimate.
-  gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator, NULL,
-                         sample_time_nanos, /*reset_stats=*/false);
+  sampleRateEstimatorUpdate(&gyro_cal->debug_gyro_cal.sample_rate_estimator,
+                            sample_time_nanos);
 #endif  // GYRO_CAL_DBG_ENABLED
 
   // Pass gyro data to stillness detector
@@ -396,7 +368,7 @@
 
   // Determines if the device is currently still.
   device_is_still = (conf_still > gyro_cal->stillness_threshold) &&
-      !mean_not_stable && !min_max_temp_exceeded;
+                    !mean_not_stable && !min_max_temp_exceeded;
 
   if (device_is_still) {
     // Device is "still" logic:
@@ -440,12 +412,6 @@
       computeGyroCal(gyro_cal,
                      gyro_cal->gyro_stillness_detect.last_sample_time);
 
-#ifdef GYRO_CAL_DBG_ENABLED
-      // Resets the sampling rate estimate.
-      gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator, NULL,
-                             sample_time_nanos, /*reset_stats=*/true);
-#endif  // GYRO_CAL_DBG_ENABLED
-
       // Update stillness flag. Force the start of a new stillness period.
       gyro_cal->prev_still = false;
     } else {
@@ -484,12 +450,6 @@
     gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);
     gyroStillMeanTracker(gyro_cal, DO_RESET);
 
-#ifdef GYRO_CAL_DBG_ENABLED
-    // Resets the sampling rate estimate.
-    gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator, NULL,
-                           sample_time_nanos, /*reset_stats=*/true);
-#endif  // GYRO_CAL_DBG_ENABLED
-
     // Update stillness flag.
     gyro_cal->prev_still = false;
   }
@@ -501,11 +461,11 @@
 // Calculates a new gyro bias offset calibration value.
 void computeGyroCal(struct GyroCal* gyro_cal, uint64_t calibration_time_nanos) {
   // Check to see if new calibration values is within acceptable range.
-  if (!(gyro_cal->gyro_stillness_detect.prev_mean_x <  MAX_GYRO_BIAS &&
+  if (!(gyro_cal->gyro_stillness_detect.prev_mean_x < MAX_GYRO_BIAS &&
         gyro_cal->gyro_stillness_detect.prev_mean_x > -MAX_GYRO_BIAS &&
-        gyro_cal->gyro_stillness_detect.prev_mean_y <  MAX_GYRO_BIAS &&
+        gyro_cal->gyro_stillness_detect.prev_mean_y < MAX_GYRO_BIAS &&
         gyro_cal->gyro_stillness_detect.prev_mean_y > -MAX_GYRO_BIAS &&
-        gyro_cal->gyro_stillness_detect.prev_mean_z <  MAX_GYRO_BIAS &&
+        gyro_cal->gyro_stillness_detect.prev_mean_z < MAX_GYRO_BIAS &&
         gyro_cal->gyro_stillness_detect.prev_mean_z > -MAX_GYRO_BIAS)) {
 #ifdef GYRO_CAL_DBG_ENABLED
     CAL_DEBUG_LOG(
@@ -607,12 +567,6 @@
     gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);
     gyroStillMeanTracker(gyro_cal, DO_RESET);
 
-#ifdef GYRO_CAL_DBG_ENABLED
-    // Resets the sampling rate estimate.
-    gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator, NULL,
-                           sample_time_nanos, /*reset_stats=*/true);
-#endif  // GYRO_CAL_DBG_ENABLED
-
     // Resets the stillness window end-time.
     gyro_cal->stillness_win_endtime_nanos = 0;
 
@@ -631,7 +585,6 @@
     }
 
     // Assert watchdog timeout flags.
-    gyro_cal->gyro_watchdog_timeout |= watchdog_timeout;
     gyro_cal->gyro_watchdog_start_nanos = 0;
   }
 }
@@ -833,53 +786,6 @@
 }
 
 #ifdef GYRO_CAL_DBG_ENABLED
-void gyroSamplingRateUpdate(struct SampleRateData* sample_rate_estimator,
-                            float* debug_mean_sampling_rate_hz,
-                            uint64_t timestamp_nanos, bool reset_stats) {
-  // If 'debug_mean_sampling_rate_hz' is not NULL then this function just reads
-  // out the estimate of the sampling rate.
-  if (debug_mean_sampling_rate_hz) {
-    if (sample_rate_estimator->num_samples > 1 &&
-        sample_rate_estimator->time_delta_accumulator > 0) {
-      // Computes the final mean calculation.
-      *debug_mean_sampling_rate_hz =
-          sample_rate_estimator->num_samples /
-          (floatFromUint64(sample_rate_estimator->time_delta_accumulator) *
-           NANOS_TO_SEC);
-    } else {
-      // Not enough samples to compute a valid sample rate estimate. Indicate
-      // this with a -1 value.
-      *debug_mean_sampling_rate_hz = -1.0f;
-    }
-    reset_stats = true;
-  }
-
-  // Resets the sampling rate mean estimator data.
-  if (reset_stats) {
-    sample_rate_estimator->last_timestamp_nanos = 0;
-    sample_rate_estimator->time_delta_accumulator = 0;
-    sample_rate_estimator->num_samples = 0;
-    return;
-  }
-
-  // Skip adding this data to the accumulator if:
-  //   1. A bad timestamp was received (i.e., time not monotonic).
-  //   2. 'last_timestamp_nanos' is zero.
-  if (timestamp_nanos <= sample_rate_estimator->last_timestamp_nanos ||
-      sample_rate_estimator->last_timestamp_nanos == 0) {
-    sample_rate_estimator->last_timestamp_nanos = timestamp_nanos;
-    return;
-  }
-
-  // Increments the number of samples.
-  sample_rate_estimator->num_samples++;
-
-  // Accumulate the time steps.
-  sample_rate_estimator->time_delta_accumulator +=
-      timestamp_nanos - sample_rate_estimator->last_timestamp_nanos;
-  sample_rate_estimator->last_timestamp_nanos = timestamp_nanos;
-}
-
 void gyroCalUpdateDebug(struct GyroCal* gyro_cal) {
   // Only update this data if debug printing is not currently in progress
   // (i.e., don't want to risk overwriting debug information that is actively
@@ -912,11 +818,6 @@
   gyro_cal->debug_gyro_cal.calibration[1] = gyro_cal->bias_y;
   gyro_cal->debug_gyro_cal.calibration[2] = gyro_cal->bias_z;
 
-  // Records the mean gyroscope sampling rate.
-  gyroSamplingRateUpdate(&gyro_cal->sample_rate_estimator,
-                         &gyro_cal->debug_gyro_cal.mean_sampling_rate_hz, 0,
-                         /*reset_stats=*/true);
-
   // Records the min/max gyroscope window stillness mean values.
   memcpy(gyro_cal->debug_gyro_cal.gyro_winmean_min, gyro_cal->gyro_winmean_min,
          sizeof(gyro_cal->gyro_winmean_min));
@@ -1029,7 +930,9 @@
           CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.temperature_max_celsius -
                                gyro_cal->debug_gyro_cal.temperature_min_celsius,
                            3),
-          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mean_sampling_rate_hz, 3));
+          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.sample_rate_estimator
+                               .mean_sampling_rate_estimate_hz,
+                           3));
       break;
 
     case GYRO_MINMAX_STILLNESS_MEAN:
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_cal.h b/firmware/os/algos/calibration/gyroscope/gyro_cal.h
index 5e7d5ee..837a4c0 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_cal.h
+++ b/firmware/os/algos/calibration/gyroscope/gyro_cal.h
@@ -43,6 +43,10 @@
 
 #include "calibration/gyroscope/gyro_stillness_detect.h"
 
+#ifdef GYRO_CAL_DBG_ENABLED
+#include "calibration/sample_rate_estimator/sample_rate_estimator.h"
+#endif  // GYRO_CAL_DBG_ENABLED
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -63,10 +67,10 @@
 
 // Gyro Cal debug information/data tracking structure.
 struct DebugGyroCal {
+  struct SampleRateEstimator sample_rate_estimator;
   uint64_t start_still_time_nanos;
   uint64_t end_still_time_nanos;
   uint64_t stillness_duration_nanos;
-  float mean_sampling_rate_hz;
   float accel_stillness_conf;
   float gyro_stillness_conf;
   float mag_stillness_conf;
@@ -84,15 +88,29 @@
   float temperature_mean_celsius;
   bool using_mag_sensor;
 };
-
-// Data structure for sample rate estimation.
-struct SampleRateData {
-  uint64_t last_timestamp_nanos;
-  uint64_t time_delta_accumulator;
-  size_t num_samples;
-};
 #endif  // GYRO_CAL_DBG_ENABLED
 
+// GyroCal algorithm parameters (see GyroCal and GyroStillDet for details).
+struct GyroCalParameters {
+  uint64_t min_still_duration_nanos;
+  uint64_t max_still_duration_nanos;
+  uint64_t calibration_time_nanos;
+  uint64_t window_time_duration_nanos;
+  float bias_x;  // units: radians per second
+  float bias_y;
+  float bias_z;
+  float stillness_threshold;         // units: (radians per second)^2
+  float stillness_mean_delta_limit;  // units: radians per second
+  float gyro_var_threshold;          // units: (radians per second)^2
+  float gyro_confidence_delta;       // units: (radians per second)^2
+  float accel_var_threshold;         // units: (meters per second)^2
+  float accel_confidence_delta;      // units: (meters per second)^2
+  float mag_var_threshold;           // units: micro-tesla^2
+  float mag_confidence_delta;        // units: micro-tesla^2
+  float temperature_delta_limit_celsius;
+  bool gyro_calibration_enable;
+};
+
 // Data structure for tracking min/max window mean during device stillness.
 struct MinMaxWindowMeanData {
   float gyro_winmean_min[3];
@@ -149,7 +167,6 @@
   // Watchdog timer to reset to a known good state when data capture stalls.
   uint64_t gyro_watchdog_start_nanos;
   uint64_t gyro_watchdog_timeout_duration_nanos;
-  bool gyro_watchdog_timeout;
 
   // Flag is "true" when the magnetometer is used.
   bool using_mag_sensor;
@@ -177,7 +194,7 @@
   float temperature_mean_celsius;
   float temperature_delta_limit_celsius;
 
-//----------------------------------------------------------------
+  //----------------------------------------------------------------
 
 #ifdef GYRO_CAL_DBG_ENABLED
   // Debug info.
@@ -185,9 +202,6 @@
   enum GyroCalDebugState debug_state;  // Debug printout state machine.
   enum GyroCalDebugState next_state;   // Debug state machine next state.
   uint64_t wait_timer_nanos;           // Debug message throttle timer.
-
-  struct SampleRateData sample_rate_estimator;  // Debug sample rate estimator.
-
   size_t debug_calibration_count;      // Total number of cals performed.
   size_t debug_watchdog_count;         // Total number of watchdog timeouts.
   bool debug_print_trigger;            // Flag used to trigger data printout.
@@ -197,27 +211,21 @@
 /////// FUNCTION PROTOTYPES //////////////////////////////////////////
 
 // Initialize the gyro calibration data structure.
-void gyroCalInit(struct GyroCal* gyro_cal, uint64_t min_still_duration,
-                 uint64_t max_still_duration_nanos, float bias_x, float bias_y,
-                 float bias_z, uint64_t calibration_time_nanos,
-                 uint64_t window_time_duration_nanos, float gyro_var_threshold,
-                 float gyro_confidence_delta, float accel_var_threshold,
-                 float accel_confidence_delta, float mag_var_threshold,
-                 float mag_confidence_delta, float stillness_threshold,
-                 float stillness_mean_delta_limit,
-                 float temperature_delta_limit_celsius,
-                 bool gyro_calibration_enable);
+void gyroCalInit(struct GyroCal* gyro_cal,
+                 const struct GyroCalParameters* parameters);
 
 // Void all pointers in the gyro calibration data structure.
 void gyroCalDestroy(struct GyroCal* gyro_cal);
 
 // Get the most recent bias calibration value.
 void gyroCalGetBias(struct GyroCal* gyro_cal, float* bias_x, float* bias_y,
-                    float* bias_z, float* temperature_celsius);
+                    float* bias_z, float* temperature_celsius,
+                    uint64_t* calibration_time_nanos);
 
 // Set an initial bias calibration value.
 void gyroCalSetBias(struct GyroCal* gyro_cal, float bias_x, float bias_y,
-                    float bias_z, uint64_t calibration_time_nanos);
+                    float bias_z, float temperature_celsius,
+                    uint64_t calibration_time_nanos);
 
 // Remove gyro bias from the calibration [rad/sec].
 void gyroCalRemoveBias(struct GyroCal* gyro_cal, float xi, float yi, float zi,
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
index 80f2fa2..deb26b3 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
+++ b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.c
@@ -25,8 +25,8 @@
 /////// FUNCTION DEFINITIONS /////////////////////////////////////////
 
 // Initialize the GyroStillDet structure.
-void gyroStillDetInit(struct GyroStillDet* gyro_still_det,
-                      float var_threshold, float confidence_delta) {
+void gyroStillDetInit(struct GyroStillDet* gyro_still_det, float var_threshold,
+                      float confidence_delta) {
   // Clear all data structure variables to 0.
   memset(gyro_still_det, 0, sizeof(struct GyroStillDet));
 
@@ -192,12 +192,12 @@
       // Each axis score is limited [0,1].
       tmp_denom = 1.f / (upper_var_thresh - lower_var_thresh);
       gyro_still_det->stillness_confidence =
-          gyroStillDetLimit(
-              0.5f - (gyro_still_det->win_var_x - var_thresh) * tmp_denom) *
-          gyroStillDetLimit(
-              0.5f - (gyro_still_det->win_var_y - var_thresh) * tmp_denom) *
-          gyroStillDetLimit(
-              0.5f - (gyro_still_det->win_var_z - var_thresh) * tmp_denom);
+          gyroStillDetLimit(0.5f - (gyro_still_det->win_var_x - var_thresh) *
+                                       tmp_denom) *
+          gyroStillDetLimit(0.5f - (gyro_still_det->win_var_y - var_thresh) *
+                                       tmp_denom) *
+          gyroStillDetLimit(0.5f - (gyro_still_det->win_var_z - var_thresh) *
+                                       tmp_denom);
     }
   }
 
@@ -207,8 +207,7 @@
 
 // Resets the stillness detector and initiates a new detection window.
 // 'reset_stats' determines whether the stillness statistics are reset.
-void gyroStillDetReset(struct GyroStillDet* gyro_still_det,
-                       bool reset_stats) {
+void gyroStillDetReset(struct GyroStillDet* gyro_still_det, bool reset_stats) {
   float tmp_denom = 1.f;
 
   // Reset the stillness data ready flag.
diff --git a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.h b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.h
index 9a1d876..51c4bee 100644
--- a/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.h
+++ b/firmware/os/algos/calibration/gyroscope/gyro_stillness_detect.h
@@ -60,7 +60,7 @@
   // is used to keep track of the window start time.
   bool start_new_window;
 
-  // Starting time stamp for the the current window.
+  // Starting time stamp for the current window.
   uint64_t window_start_time;
 
   // Accumulator variables for tracking the sample mean during
@@ -93,8 +93,8 @@
 /////// FUNCTION PROTOTYPES //////////////////////////////////////////
 
 // Initialize the gyro_still_det_t structure.
-void gyroStillDetInit(struct GyroStillDet* gyro_still_det,
-                      float var_threshold, float confidence_delta);
+void gyroStillDetInit(struct GyroStillDet* gyro_still_det, float var_threshold,
+                      float confidence_delta);
 
 // Update the stillness detector with a new sample.
 void gyroStillDetUpdate(struct GyroStillDet* gyro_still_det,
diff --git a/firmware/os/algos/calibration/magnetometer/mag_cal.c b/firmware/os/algos/calibration/magnetometer/mag_cal.c
index 7f8e563..40749e1 100644
--- a/firmware/os/algos/calibration/magnetometer/mag_cal.c
+++ b/firmware/os/algos/calibration/magnetometer/mag_cal.c
@@ -21,6 +21,7 @@
 
 #include "calibration/util/cal_log.h"
 
+// clang-format off
 #ifdef MAG_CAL_ORIGINAL_TUNING
 #define MAX_EIGEN_RATIO 25.0f
 #define MAX_EIGEN_MAG 80.0f          // uT
@@ -42,13 +43,14 @@
 #ifdef DIVERSITY_CHECK_ENABLED
 #define MAX_DISTANCE_VIOLATIONS 2
 #ifdef SPHERE_FIT_ENABLED
-# define MAX_ITERATIONS 30
-# define INITIAL_U_SCALE 1.0e-4f
-# define GRADIENT_THRESHOLD 1.0e-16f
-# define RELATIVE_STEP_THRESHOLD 1.0e-7f
-# define FROM_MICRO_SEC_TO_SEC 1.0e-6f
+#define MAX_ITERATIONS 30
+#define INITIAL_U_SCALE 1.0e-4f
+#define GRADIENT_THRESHOLD 1.0e-16f
+#define RELATIVE_STEP_THRESHOLD 1.0e-7f
+#define FROM_MICRO_SEC_TO_SEC 1.0e-6f
 #endif
 #endif
+// clang-format
 
 // eigen value magnitude and ratio test
 static int moc_eigen_test(struct KasaFit *kasa) {
@@ -82,71 +84,8 @@
   return eigen_pass;
 }
 
-// Kasa sphere fitting with normal equation
-int magKasaFit(struct KasaFit *kasa, struct Vec3 *bias, float *radius) {
-  //    A    *   out   =    b
-  // (4 x 4)   (4 x 1)   (4 x 1)
-  struct Mat44 A;
-  A.elem[0][0] = kasa->acc_xx;
-  A.elem[0][1] = kasa->acc_xy;
-  A.elem[0][2] = kasa->acc_xz;
-  A.elem[0][3] = kasa->acc_x;
-  A.elem[1][0] = kasa->acc_xy;
-  A.elem[1][1] = kasa->acc_yy;
-  A.elem[1][2] = kasa->acc_yz;
-  A.elem[1][3] = kasa->acc_y;
-  A.elem[2][0] = kasa->acc_xz;
-  A.elem[2][1] = kasa->acc_yz;
-  A.elem[2][2] = kasa->acc_zz;
-  A.elem[2][3] = kasa->acc_z;
-  A.elem[3][0] = kasa->acc_x;
-  A.elem[3][1] = kasa->acc_y;
-  A.elem[3][2] = kasa->acc_z;
-  A.elem[3][3] = 1.0f;
-
-  struct Vec4 b;
-  initVec4(&b, -kasa->acc_xw, -kasa->acc_yw, -kasa->acc_zw, -kasa->acc_w);
-
-  struct Size4 pivot;
-  mat44DecomposeLup(&A, &pivot);
-
-  struct Vec4 out;
-  mat44Solve(&A, &out, &b, &pivot);
-
-  // sphere: (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2
-  //
-  // xc = -out[0] / 2, yc = -out[1] / 2, zc = -out[2] / 2
-  // r = sqrt(xc^2 + yc^2 + zc^2 - out[3])
-
-  struct Vec3 v;
-  initVec3(&v, out.x, out.y, out.z);
-  vec3ScalarMul(&v, -0.5f);
-
-  float r_square = vec3Dot(&v, &v) - out.w;
-  float r = (r_square > 0) ? sqrtf(r_square) : 0;
-
-  initVec3(bias, v.x, v.y, v.z);
-  *radius = r;
-
-  int success = 0;
-  if (r > MIN_FIT_MAG && r < MAX_FIT_MAG) {
-    success = 1;
-  }
-
-  return success;
-}
-
-void magKasaReset(struct KasaFit *kasa) {
-  kasa->acc_x = kasa->acc_y = kasa->acc_z = kasa->acc_w = 0.0f;
-  kasa->acc_xx = kasa->acc_xy = kasa->acc_xz = kasa->acc_xw = 0.0f;
-  kasa->acc_yy = kasa->acc_yz = kasa->acc_yw = 0.0f;
-  kasa->acc_zz = kasa->acc_zw = 0.0f;
-
-  kasa->nsamples = 0;
-}
-
 void magCalReset(struct MagCal *moc) {
-  magKasaReset(&moc->kasa);
+  kasaReset(&moc->kasa);
 #ifdef DIVERSITY_CHECK_ENABLED
   diversityCheckerReset(&moc->diversity_checker);
 #endif
@@ -154,12 +93,12 @@
   moc->kasa_batching = false;
 }
 
-static int moc_batch_complete(struct MagCal *moc, uint64_t sample_time_us) {
-  int complete = 0;
+static bool moc_batch_complete(struct MagCal *moc, uint64_t sample_time_us) {
+  bool complete = false;
 
   if ((sample_time_us - moc->start_time > moc->min_batch_window_in_micros) &&
       (moc->kasa.nsamples > MIN_BATCH_SIZE)) {
-    complete = 1;
+    complete = true;
 
   } else if (sample_time_us - moc->start_time > MAX_BATCH_WINDOW) {
     // not enough samples collected in MAX_BATCH_WINDOW or too many
@@ -170,42 +109,32 @@
   return complete;
 }
 
-void initKasa(struct KasaFit *kasa) {
-  magKasaReset(kasa);
-}
-
-void initMagCal(struct MagCal *moc, float x_bias, float y_bias, float z_bias,
-                float c00, float c01, float c02, float c10, float c11,
-                float c12, float c20, float c21, float c22,
-                uint32_t min_batch_window_in_micros
+void initMagCal(struct MagCal *moc,
+                const struct MagCalParameters *mag_cal_parameters
 #ifdef DIVERSITY_CHECK_ENABLED
-                ,size_t min_num_diverse_vectors
-                ,size_t max_num_max_distance
-                ,float var_threshold
-                ,float max_min_threshold
-                ,float local_field
-                ,float threshold_tuning_param
-                ,float max_distance_tuning_param
+                ,
+                const struct DiversityCheckerParameters *diverse_parameters
 #endif
-                ) {
+) {
   magCalReset(moc);
   moc->update_time = 0;
-  moc->min_batch_window_in_micros = min_batch_window_in_micros;
+  moc->min_batch_window_in_micros =
+      mag_cal_parameters->min_batch_window_in_micros;
   moc->radius = 0.0f;
 
-  moc->x_bias = x_bias;
-  moc->y_bias = y_bias;
-  moc->z_bias = z_bias;
+  moc->x_bias = mag_cal_parameters->x_bias;
+  moc->y_bias = mag_cal_parameters->y_bias;
+  moc->z_bias = mag_cal_parameters->z_bias;
 
-  moc->c00 = c00;
-  moc->c01 = c01;
-  moc->c02 = c02;
-  moc->c10 = c10;
-  moc->c11 = c11;
-  moc->c12 = c12;
-  moc->c20 = c20;
-  moc->c21 = c21;
-  moc->c22 = c22;
+  moc->c00 = mag_cal_parameters->c00;
+  moc->c01 = mag_cal_parameters->c01;
+  moc->c02 = mag_cal_parameters->c02;
+  moc->c10 = mag_cal_parameters->c10;
+  moc->c11 = mag_cal_parameters->c11;
+  moc->c12 = mag_cal_parameters->c12;
+  moc->c20 = mag_cal_parameters->c20;
+  moc->c21 = mag_cal_parameters->c21;
+  moc->c22 = mag_cal_parameters->c22;
 
 #ifdef MAG_CAL_DEBUG_ENABLE
   moc->mag_dbg.mag_trigger_count = 0;
@@ -214,14 +143,7 @@
 
 #ifdef DIVERSITY_CHECK_ENABLED
   // Diversity Checker
-  diversityCheckerInit(&moc->diversity_checker,
-                       min_num_diverse_vectors,
-                       max_num_max_distance,
-                       var_threshold,
-                       max_min_threshold,
-                       local_field,
-                       threshold_tuning_param,
-                       max_distance_tuning_param);
+  diversityCheckerInit(&moc->diversity_checker, diverse_parameters);
 #endif
 }
 
@@ -237,117 +159,70 @@
 #endif
 
   // 1. run accumulators
-  float w = x * x + y * y + z * z;
+  kasaAccumulate(&moc->kasa, x, y, z);
 
-  moc->kasa.acc_x += x;
-  moc->kasa.acc_y += y;
-  moc->kasa.acc_z += z;
-  moc->kasa.acc_w += w;
-
-  moc->kasa.acc_xx += x * x;
-  moc->kasa.acc_xy += x * y;
-  moc->kasa.acc_xz += x * z;
-  moc->kasa.acc_xw += x * w;
-
-  moc->kasa.acc_yy += y * y;
-  moc->kasa.acc_yz += y * z;
-  moc->kasa.acc_yw += y * w;
-
-  moc->kasa.acc_zz += z * z;
-  moc->kasa.acc_zw += z * w;
-
-  if (++moc->kasa.nsamples == 1) {
+  if (moc->kasa.nsamples == 1) {
     moc->start_time = sample_time_us;
     moc->kasa_batching = true;
   }
 
   // 2. batch has enough samples?
   if (moc_batch_complete(moc, sample_time_us)) {
-    float inv = 1.0f / moc->kasa.nsamples;
-
-    moc->kasa.acc_x *= inv;
-    moc->kasa.acc_y *= inv;
-    moc->kasa.acc_z *= inv;
-    moc->kasa.acc_w *= inv;
-
-    moc->kasa.acc_xx *= inv;
-    moc->kasa.acc_xy *= inv;
-    moc->kasa.acc_xz *= inv;
-    moc->kasa.acc_xw *= inv;
-
-    moc->kasa.acc_yy *= inv;
-    moc->kasa.acc_yz *= inv;
-    moc->kasa.acc_yw *= inv;
-
-    moc->kasa.acc_zz *= inv;
-    moc->kasa.acc_zw *= inv;
+    kasaNormalize(&moc->kasa);
 
     // 3. eigen test
     if (moc_eigen_test(&moc->kasa)) {
       struct Vec3 bias;
       float radius;
       // 4. Kasa sphere fitting
-      if (magKasaFit(&moc->kasa, &bias, &radius)) {
-
+      if (kasaFit(&moc->kasa, &bias, &radius, MAX_FIT_MAG, MIN_FIT_MAG)) {
 #ifdef MAG_CAL_DEBUG_ENABLE
         moc->mag_dbg.kasa_count++;
         CAL_DEBUG_LOG("[MAG_CAL:KASA UPDATE] :,",
                       "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %lu, %lu",
-                      CAL_ENCODE_FLOAT(bias.x, 3),
-                      CAL_ENCODE_FLOAT(bias.y, 3),
-                      CAL_ENCODE_FLOAT(bias.z, 3),
-                      CAL_ENCODE_FLOAT(radius, 3),
+                      CAL_ENCODE_FLOAT(bias.x, 3), CAL_ENCODE_FLOAT(bias.y, 3),
+                      CAL_ENCODE_FLOAT(bias.z, 3), CAL_ENCODE_FLOAT(radius, 3),
                       (unsigned long int)moc->mag_dbg.kasa_count,
                       (unsigned long int)moc->mag_dbg.mag_trigger_count);
 #endif
 
 #ifdef DIVERSITY_CHECK_ENABLED
         // Update the local field.
-        diversityCheckerLocalFieldUpdate(&moc->diversity_checker,
-                                         radius);
+        diversityCheckerLocalFieldUpdate(&moc->diversity_checker, radius);
 
         // checking if data is diverse.
-        if (diversityCheckerNormQuality(&moc->diversity_checker,
-                                        bias.x,
-                                        bias.y,
+        if (diversityCheckerNormQuality(&moc->diversity_checker, bias.x, bias.y,
                                         bias.z) &&
-            moc->diversity_checker.num_max_dist_violations
-            <= MAX_DISTANCE_VIOLATIONS) {
-
+            moc->diversity_checker.num_max_dist_violations <=
+                MAX_DISTANCE_VIOLATIONS) {
           // DEBUG PRINT OUT.
 #ifdef MAG_CAL_DEBUG_ENABLE
           moc->mag_dbg.mag_trigger_count++;
 #ifdef DIVERSE_DEBUG_ENABLE
           moc->diversity_checker.diversity_dbg.new_trigger = 1;
-          CAL_DEBUG_LOG("[MAG_CAL:BIAS UPDATE] :, ",
-                        "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%06d,"
-                        "%s%d.%03d, %s%d.%03d, %s%d.%03d, %zu, %s%d.%03d, "
-                        "%s%d.%03d, %lu, %lu, %llu, %s%d.%03d, %s%d.%03d, "
-                        "%s%d.%03d, %llu",
-                        CAL_ENCODE_FLOAT(bias.x, 3),
-                        CAL_ENCODE_FLOAT(bias.y, 3),
-                        CAL_ENCODE_FLOAT(bias.z, 3),
-                        CAL_ENCODE_FLOAT(radius, 3),
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.diversity_dbg.var_log, 6),
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.diversity_dbg.mean_log, 3),
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.diversity_dbg.max_log, 3),
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.diversity_dbg.min_log, 3),
-                        moc->diversity_checker.num_points,
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.threshold, 3),
-                        CAL_ENCODE_FLOAT(
-                            moc->diversity_checker.max_distance, 3),
-                        (unsigned long int)moc->mag_dbg.mag_trigger_count,
-                        (unsigned long int)moc->mag_dbg.kasa_count,
-                        (unsigned long long int)sample_time_us,
-                        CAL_ENCODE_FLOAT(moc->x_bias, 3),
-                        CAL_ENCODE_FLOAT(moc->y_bias, 3),
-                        CAL_ENCODE_FLOAT(moc->z_bias, 3),
-                        (unsigned long long int)moc->update_time);
+          CAL_DEBUG_LOG(
+              "[MAG_CAL:BIAS UPDATE] :, ",
+              "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%06d,"
+              "%s%d.%03d, %s%d.%03d, %s%d.%03d, %zu, %s%d.%03d, "
+              "%s%d.%03d, %lu, %lu, %llu, %s%d.%03d, %s%d.%03d, "
+              "%s%d.%03d, %llu",
+              CAL_ENCODE_FLOAT(bias.x, 3), CAL_ENCODE_FLOAT(bias.y, 3),
+              CAL_ENCODE_FLOAT(bias.z, 3), CAL_ENCODE_FLOAT(radius, 3),
+              CAL_ENCODE_FLOAT(moc->diversity_checker.diversity_dbg.var_log, 6),
+              CAL_ENCODE_FLOAT(moc->diversity_checker.diversity_dbg.mean_log,
+                               3),
+              CAL_ENCODE_FLOAT(moc->diversity_checker.diversity_dbg.max_log, 3),
+              CAL_ENCODE_FLOAT(moc->diversity_checker.diversity_dbg.min_log, 3),
+              moc->diversity_checker.num_points,
+              CAL_ENCODE_FLOAT(moc->diversity_checker.threshold, 3),
+              CAL_ENCODE_FLOAT(moc->diversity_checker.max_distance, 3),
+              (unsigned long int)moc->mag_dbg.mag_trigger_count,
+              (unsigned long int)moc->mag_dbg.kasa_count,
+              (unsigned long long int)sample_time_us,
+              CAL_ENCODE_FLOAT(moc->x_bias, 3),
+              CAL_ENCODE_FLOAT(moc->y_bias, 3),
+              CAL_ENCODE_FLOAT(moc->z_bias, 3),
+              (unsigned long long int)moc->update_time);
 #endif
 #endif
 #endif
@@ -416,102 +291,100 @@
 #if defined MAG_CAL_DEBUG_ENABLE && defined DIVERSE_DEBUG_ENABLE
 // This function prints every second sample parts of the dbg diverse_data_log,
 // which ensures that all the messages get printed into the log file.
-void magLogPrint(struct DiversityChecker* diverse_data, float temp) {
+void magLogPrint(struct DiversityChecker *diverse_data, float temp) {
   // Sample counter.
   static size_t sample_counter = 0;
-  const float* data_log_ptr =
-      &diverse_data->diversity_dbg.diverse_data_log[0];
+  const float *data_log_ptr = &diverse_data->diversity_dbg.diverse_data_log[0];
   if (diverse_data->diversity_dbg.new_trigger == 1) {
     sample_counter++;
     if (sample_counter == 2) {
-      CAL_DEBUG_LOG("[MAG_CAL:MEMORY X] :,",
-                    "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
-                    ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d",
-                    (unsigned long int)diverse_data->
-                    diversity_dbg.diversity_count,
-                    CAL_ENCODE_FLOAT(data_log_ptr[0*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[1*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[2*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[3*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[4*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[5*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[6*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[7*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[8*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[9*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[10*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[11*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[12*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[13*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[14*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[15*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[16*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[17*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[18*3], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[19*3], 3),
-                    CAL_ENCODE_FLOAT(temp, 3));
+      CAL_DEBUG_LOG(
+          "[MAG_CAL:MEMORY X] :,",
+          "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
+          ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d",
+          (unsigned long int)diverse_data->diversity_dbg.diversity_count,
+          CAL_ENCODE_FLOAT(data_log_ptr[0 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[1 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[2 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[3 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[4 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[5 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[6 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[7 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[8 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[9 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[10 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[11 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[12 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[13 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[14 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[15 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[16 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[17 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[18 * 3], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[19 * 3], 3), CAL_ENCODE_FLOAT(temp, 3));
     }
 
     if (sample_counter == 4) {
-      CAL_DEBUG_LOG("[MAG_CAL:MEMORY Y] :,",
-                    "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
-                    ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, ",
-                    (unsigned long int)diverse_data->
-                    diversity_dbg.diversity_count,
-                    CAL_ENCODE_FLOAT(data_log_ptr[0*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[1*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[2*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[3*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[4*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[5*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[6*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[7*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[8*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[9*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[10*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[11*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[12*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[13*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[14*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[15*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[16*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[17*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[18*3+1], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[19*3+1], 3));
+      CAL_DEBUG_LOG(
+          "[MAG_CAL:MEMORY Y] :,",
+          "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
+          ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, ",
+          (unsigned long int)diverse_data->diversity_dbg.diversity_count,
+          CAL_ENCODE_FLOAT(data_log_ptr[0 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[1 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[2 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[3 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[4 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[5 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[6 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[7 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[8 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[9 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[10 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[11 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[12 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[13 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[14 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[15 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[16 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[17 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[18 * 3 + 1], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[19 * 3 + 1], 3));
     }
     if (sample_counter == 6) {
-      CAL_DEBUG_LOG("[MAG_CAL:MEMORY Z] :,",
-                    "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
-                    ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
-                    "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, ",
-                    (unsigned long int)diverse_data->
-                    diversity_dbg.diversity_count,
-                    CAL_ENCODE_FLOAT(data_log_ptr[0*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[1*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[2*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[3*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[4*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[5*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[6*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[7*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[8*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[9*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[10*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[11*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[12*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[13*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[14*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[15*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[16*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[17*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[18*3+2], 3),
-                    CAL_ENCODE_FLOAT(data_log_ptr[19*3+2], 3));
+      CAL_DEBUG_LOG(
+          "[MAG_CAL:MEMORY Z] :,",
+          "%lu, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d"
+          ", %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, "
+          "%s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, %s%d.%03d, ",
+          (unsigned long int)diverse_data->diversity_dbg.diversity_count,
+          CAL_ENCODE_FLOAT(data_log_ptr[0 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[1 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[2 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[3 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[4 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[5 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[6 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[7 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[8 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[9 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[10 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[11 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[12 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[13 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[14 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[15 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[16 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[17 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[18 * 3 + 2], 3),
+          CAL_ENCODE_FLOAT(data_log_ptr[19 * 3 + 2], 3));
       sample_counter = 0;
       diverse_data->diversity_dbg.new_trigger = 0;
     }
diff --git a/firmware/os/algos/calibration/magnetometer/mag_cal.h b/firmware/os/algos/calibration/magnetometer/mag_cal.h
index 8c9da6f..381a925 100644
--- a/firmware/os/algos/calibration/magnetometer/mag_cal.h
+++ b/firmware/os/algos/calibration/magnetometer/mag_cal.h
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_CAL_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_CAL_H_
 
@@ -26,8 +27,9 @@
 #include <stdint.h>
 #include <sys/types.h>
 #ifdef DIVERSITY_CHECK_ENABLED
-#include "calibration/common/diversity_checker.h"
+#include "calibration/diversity_checker/diversity_checker.h"
 #endif
+#include "common/math/kasa.h"
 #include "common/math/mat.h"
 #include "common/math/vec.h"
 
@@ -35,17 +37,13 @@
 extern "C" {
 #endif
 
-struct KasaFit {
-  float acc_x, acc_y, acc_z, acc_w;
-  float acc_xx, acc_xy, acc_xz, acc_xw;
-  float acc_yy, acc_yz, acc_yw, acc_zz, acc_zw;
-  size_t nsamples;
-};
-
 enum MagUpdate {
   NO_UPDATE = 0x00,
   UPDATE_BIAS = 0x01,
   UPDATE_SPHERE_FIT = 0x02,
+  UPDATE_BIAS_MAGGYRO_MEDIUM = 0x04,
+  UPDATE_BIAS_MAGGYRO_HIGH = 0x08,
+  MAGGYRO_TIMEOUT = 0x10,
 };
 
 #ifdef MAG_CAL_DEBUG_ENABLE
@@ -55,17 +53,34 @@
 };
 #endif
 
+// MagCal algorithm parameters (see MagCal for details).
+struct MagCalParameters {
+  uint32_t min_batch_window_in_micros;
+  float x_bias;  // [micro-Tesla]
+  float y_bias;  // [micro-Tesla]
+  float z_bias;  // [micro-Tesla]
+  float c00;
+  float c01;
+  float c02;
+  float c10;
+  float c11;
+  float c12;
+  float c20;
+  float c21;
+  float c22;
+};
+
 struct MagCal {
 #ifdef DIVERSITY_CHECK_ENABLED
   struct DiversityChecker diversity_checker;
 #endif
   struct KasaFit kasa;
 
-  uint64_t start_time;
-  uint64_t update_time;
+  uint64_t start_time;   // [micro-seconds]
+  uint64_t update_time;  // [micro-seconds]
   uint32_t min_batch_window_in_micros;
   float x_bias, y_bias, z_bias;
-  float radius;
+  float radius;  // [micro-Tesla]
   bool kasa_batching;
   float c00, c01, c02, c10, c11, c12, c20, c21, c22;
 
@@ -74,21 +89,13 @@
 #endif
 };
 
-void initKasa(struct KasaFit *kasa);
-
 #ifdef DIVERSITY_CHECK_ENABLED
-void initMagCal(struct MagCal *moc, float x_bias, float y_bias, float z_bias,
-                float c00, float c01, float c02, float c10, float c11,
-                float c12, float c20, float c21, float c22,
-                uint32_t min_batch_window_in_micros,
-                size_t min_num_diverse_vectors, size_t max_num_max_distance,
-                float var_threshold, float max_min_threshold, float local_field,
-                float threshold_tuning_param, float max_distance_tuning_param);
+void initMagCal(struct MagCal *moc,
+                const struct MagCalParameters *mag_cal_parameters,
+                const struct DiversityCheckerParameters *diverse_parameters);
 #else
-void initMagCal(struct MagCal *moc, float x_bias, float y_bias, float z_bias,
-                float c00, float c01, float c02, float c10, float c11,
-                float c12, float c20, float c21, float c22,
-                uint32_t min_batch_window_in_micros);
+void initMagCal(struct MagCal *moc,
+                const struct MagCalParameters *mag_cal_parameters);
 #endif
 
 void magCalDestroy(struct MagCal *moc);
@@ -110,12 +117,8 @@
 void magCalRemoveSoftiron(struct MagCal *moc, float xi, float yi, float zi,
                           float *xo, float *yo, float *zo);
 
-void magKasaReset(struct KasaFit *kasa);
-
 void magCalReset(struct MagCal *moc);
 
-int magKasaFit(struct KasaFit *kasa, struct Vec3 *bias, float *radius);
-
 #if defined MAG_CAL_DEBUG_ENABLE && defined DIVERSITY_CHECK_ENABLED
 void magLogPrint(struct DiversityChecker *moc, float temp);
 #endif
diff --git a/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.c b/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.c
new file mode 100644
index 0000000..29a7ba0
--- /dev/null
+++ b/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.c
@@ -0,0 +1,271 @@
+#include "calibration/magnetometer/mag_gyro_cal.h"
+
+#include <string.h>
+
+#include "calibration/util/cal_log.h"
+#include "common/math/macros.h"
+
+#define SINGULAR_TOL (1e-4f)
+
+static bool allDiagonalsLessThan(struct Mat33 *matrix, float threshold) {
+  return matrix->elem[0][0] < threshold && matrix->elem[1][1] < threshold &&
+         matrix->elem[2][2] < threshold;
+}
+
+static void calculatingAndSettingBias(struct MagGyroCal *mgc,
+                                      struct Mat33 *first_sum_inverse,
+                                      struct Vec3 *bias) {
+  // Calculate bias, since we have high trust into this estimates.
+  mat33Apply(bias, first_sum_inverse, &mgc->mgac.second_sum_average);
+
+  // Setting bias update.
+  mgc->x_bias = bias->x;
+  mgc->y_bias = bias->y;
+  mgc->z_bias = bias->z;
+}
+
+void magGyroCalBatchReset(struct MagGyroBatchCal *mgbc) {
+  mgbc->first_sample = true;
+  mgbc->start_time_ns = 0;
+  mgbc->timestamp_buffer_ns = 0;
+  initZeroMatrix(&mgbc->first_sum_update);
+  initVec3(&mgbc->last_mag_sample, 0.0f, 0.0f, 0.0f);
+  initVec3(&mgbc->second_sum_update, 0.0f, 0.0f, 0.0f);
+}
+
+static void magGyroCalAverageReset(struct MagGyroAverageCal *mgac) {
+  initZeroMatrix(&mgac->first_sum_average);
+  initVec3(&mgac->second_sum_average, 0.0f, 0.0f, 0.0f);
+  initVec3(&mgac->confidence_metric, 0.0f, 0.0f, 0.0f);
+  mgac->medium_accuracy_bias_update = false;
+  initVec3(&mgac->bias_running, 0.0f, 0.0f, 0.0f);
+}
+
+void magGyroCalReset(struct MagGyroCal *mgc) {
+  // Reset Batch Cal.
+  magGyroCalBatchReset(&mgc->mgbc);
+
+  // Reset Avarge Cal.
+  magGyroCalAverageReset(&mgc->mgac);
+
+  initVec3(&mgc->mgac.confidence_metric,
+           mgc->algo_parameters.confidence_threshold_start,
+           mgc->algo_parameters.confidence_threshold_start,
+           mgc->algo_parameters.confidence_threshold_start);
+}
+
+void magGyroCalGetBias(struct MagGyroCal *mgc, float *x, float *y, float *z) {
+  *x = mgc->x_bias;
+  *y = mgc->y_bias;
+  *z = mgc->z_bias;
+}
+
+void magGyroCalInit(struct MagGyroCal *mgc,
+                    const struct MagGyroCalParameters *params) {
+  // Tuning parameters.
+  memcpy(&mgc->algo_parameters, params, sizeof(mgc->algo_parameters));
+
+  // Bias Updates set to zero.
+  mgc->x_bias = 0.0f;
+  mgc->y_bias = 0.0f;
+  mgc->z_bias = 0.0f;
+
+  // Reseting Algo.
+  magGyroCalReset(mgc);
+}
+
+void magGyroCalBatchUpdate(struct MagGyroBatchCal *mgbc,
+                           uint64_t sample_time_ns, float mag_x_ut,
+                           float mag_y_ut, float mag_z_ut, float gyro_x_rps,
+                           float gyro_y_rps, float gyro_z_rps) {
+  // local variables, only needed for math on single samples.
+  struct Vec3 mag_data;
+  struct Vec3 gyro_data;
+  struct Vec3 mag_data_dot;
+  struct Vec3 cross_mag_gyro;
+  struct Vec3 second_sum;
+  struct Vec3 yi;
+  struct Mat33 first_sum;
+  struct Mat33 w;
+  struct Mat33 w_trans;
+  float odr_hz_int;
+  // Creating gyro and mag vector.
+  initVec3(&mag_data, mag_x_ut, mag_y_ut, mag_z_ut);
+  initVec3(&gyro_data, gyro_x_rps, gyro_y_rps, gyro_z_rps);
+
+  // Saving first sample in a batch and skipping the rest, this is done in order
+  // to do the discrete time derivative. x_dot = x(n) - x(n-1).
+  if (mgbc->first_sample) {
+    initVec3(&mgbc->last_mag_sample, mag_x_ut, mag_y_ut, mag_z_ut);
+    mgbc->first_sample = false;
+    mgbc->start_time_ns = sample_time_ns;
+    mgbc->timestamp_buffer_ns = sample_time_ns;
+    return;
+  }
+  // checking if delta time can become zero or negative.
+  if (sample_time_ns <= mgbc->timestamp_buffer_ns) {
+    // Timestamp is not monotonic, resetting batch.
+    magGyroCalBatchReset(mgbc);
+    return;
+  }
+
+  // Calculating time delta between this sample and the sample before.
+  float delta_time_ns = (float)(sample_time_ns - mgbc->timestamp_buffer_ns);
+
+  // Calculating 1/deltaT = ODR [Hz], for time derivative.
+  odr_hz_int = 1 / (delta_time_ns * NANOS_TO_SEC);
+  mgbc->timestamp_buffer_ns = sample_time_ns;
+
+  // creating mag_dot.
+  // mag_data_dot =  (mag_data - last_mag_sample) * odr.
+  vec3SubVecs(&mag_data_dot, &mag_data, &mgbc->last_mag_sample);
+  vec3ScalarMul(&mag_data_dot, odr_hz_int);
+
+  // Cross product (gyro x mag).
+  vec3Cross(&cross_mag_gyro, &gyro_data, &mag_data);
+
+  // Saving data into memory, for next derivative.
+  initVec3(&mgbc->last_mag_sample, mag_x_ut, mag_y_ut, mag_z_ut);
+
+  // calculating yi = mag_dot + (gyro x mag).
+  vec3AddVecs(&yi, &mag_data_dot, &cross_mag_gyro);
+
+  // creating skew-symmetric matrix.
+  //     /   0, -w3,  w2 \.
+  // w = |  w3,   0, -w1 |.
+  //     \ -w2,  w1,  0  /.
+  w.elem[0][0] = 0;
+  w.elem[0][1] = -gyro_z_rps;
+  w.elem[0][2] = gyro_y_rps;
+  w.elem[1][0] = gyro_z_rps;
+  w.elem[1][1] = 0;
+  w.elem[1][2] = -gyro_x_rps;
+  w.elem[2][0] = -gyro_y_rps;
+  w.elem[2][1] = gyro_x_rps;
+  w.elem[2][2] = 0;
+
+  // calculating second_sum:
+  // second_sum = w^T * yi.
+  mat33Transpose(&w_trans, &w);
+  mat33Apply(&second_sum, &w_trans, &yi);
+
+  // calculating first sum:
+  // first_sum = w^T * w.
+  mat33MultiplyTransposed(&first_sum, &w, &w);
+
+  // Updating sums.
+  mat33Add(&mgbc->first_sum_update, &first_sum);
+  vec3Add(&mgbc->second_sum_update, &second_sum);
+}
+
+enum MagUpdate magGyroCalBiasUpdate(struct MagGyroCal *mgc,
+                                    struct Mat33 *first_sum_inverse) {
+  struct Vec3 bias;
+  // Testing for medium accuracy.
+  if (allDiagonalsLessThan(first_sum_inverse,
+                           mgc->algo_parameters.confidence_threshold_medium)) {
+    // Testing for high accuracy, will leave the function here if true.
+    if (allDiagonalsLessThan(first_sum_inverse,
+                             mgc->algo_parameters.confidence_threshold_high)) {
+      // Calculate bias, since we have high trust into this estimates.
+      calculatingAndSettingBias(mgc, first_sum_inverse, &bias);
+
+      // Resetting everything since we have found the bias.
+      magGyroCalReset(mgc);
+
+      // Return high accuracy indicator.
+      return UPDATE_BIAS_MAGGYRO_HIGH;
+    }
+    // Only will reach this point if accuracy is between high and medium, now
+    // we test for if we already have a medium update.
+    if (mgc->mgac.medium_accuracy_bias_update == false) {
+      // Calculate bias, since we have high trust into the estimates.
+      calculatingAndSettingBias(mgc, first_sum_inverse, &bias);
+
+      // Resetting Batch, since we are not done yet.
+      magGyroCalBatchReset(&mgc->mgbc);
+
+      // Setting medium_accuracy_bias_update to "true".
+      mgc->mgac.medium_accuracy_bias_update = true;
+
+      // Return medium accuracy indicator.
+      return UPDATE_BIAS_MAGGYRO_MEDIUM;
+
+      // already have an medium update, still calculate the actual offset, can
+      // be used for log files and manual bias extractions.
+    } else {
+      // Calculate bias, since we have high trust into the estimates.
+      mat33Apply(&bias, first_sum_inverse, &mgc->mgac.second_sum_average);
+
+      // Setting bias update.
+      mgc->mgac.bias_running = bias;
+
+      // Resetting Batch, since we are not done yet.
+      magGyroCalBatchReset(&mgc->mgbc);
+
+      // Return no update indicator.
+      return NO_UPDATE;
+    }
+  }
+  // Have not gotten to medium accuracy, reset the batch.
+  magGyroCalBatchReset(&mgc->mgbc);
+
+  // Return no update indicator.
+  return NO_UPDATE;
+}
+
+enum MagUpdate magGyroCalRun(struct MagGyroCal *mgc, uint64_t sample_time_ns,
+                             float mag_x_ut, float mag_y_ut, float mag_z_ut,
+                             float gyro_x_rps, float gyro_y_rps,
+                             float gyro_z_rps) {
+  struct Mat33 first_sum_inverse;
+  // Running samples into the batch function.
+  magGyroCalBatchUpdate(&mgc->mgbc, sample_time_ns, mag_x_ut, mag_y_ut,
+                        mag_z_ut, gyro_x_rps, gyro_y_rps, gyro_z_rps);
+
+  // Timeout, reset and start over, return MAGGYRO_TIMEOUT.
+  if ((sample_time_ns - mgc->mgbc.start_time_ns) >=
+      mgc->algo_parameters.max_run_time_in_ns) {
+    magGyroCalReset(mgc);
+    return MAGGYRO_TIMEOUT;
+  }
+
+  // Early return if batch not complete.
+  if ((sample_time_ns - mgc->mgbc.start_time_ns) <=
+      mgc->algo_parameters.batch_window_in_ns) {
+    return NO_UPDATE;
+  }
+
+  // Updating the average matrix.
+  mat33Add(&mgc->mgac.first_sum_average, &mgc->mgbc.first_sum_update);
+
+  // Testing for singular matrix.
+  if (fabsf(mat33Determinant(&mgc->mgac.first_sum_average)) > SINGULAR_TOL) {
+    // Inverse of the average system matrix.
+    mat33Invert(&first_sum_inverse, &mgc->mgac.first_sum_average);
+
+    // Testing if the confidence metric has been improved.
+    if (first_sum_inverse.elem[0][0] < mgc->mgac.confidence_metric.x &&
+        first_sum_inverse.elem[1][1] < mgc->mgac.confidence_metric.y &&
+        first_sum_inverse.elem[2][2] < mgc->mgac.confidence_metric.z) {
+      // Updating the second_sum vector.
+      vec3Add(&mgc->mgac.second_sum_average, &mgc->mgbc.second_sum_update);
+
+      // Updating the confidence metric.
+      initVec3(&mgc->mgac.confidence_metric, first_sum_inverse.elem[0][0],
+               first_sum_inverse.elem[1][1], first_sum_inverse.elem[2][2]);
+
+      // Testing for Bias update and leaving the function.
+      return magGyroCalBiasUpdate(mgc, &first_sum_inverse);
+    }
+    // No improvement, removing the last batch from the average.
+    mat33Sub(&mgc->mgac.first_sum_average, &mgc->mgbc.first_sum_update);
+
+    // Resetting Batch, since we are not done yet.
+    magGyroCalBatchReset(&mgc->mgbc);
+
+    // Returning no bias update.
+    return NO_UPDATE;
+  }
+  return NO_UPDATE;
+}
diff --git a/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.h b/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.h
new file mode 100644
index 0000000..edf92e8
--- /dev/null
+++ b/firmware/os/algos/calibration/magnetometer/mag_gyro_cal.h
@@ -0,0 +1,188 @@
+/*
+ * This module contains the algorithms for producing a magnetometer offset
+ * calibration. The algorithm is based on the paper “Adaptive Estimation of
+ * Measurement Bias in Three-Dimensional Field Sensors with Angular-Rate
+ * Sensors: Theory and Comparative Experimental Evaluation” by G. Troni and L.
+ * Whitcomb and uses gyroscope data as well as magnetometer data. We use a batch
+ * method, resulting in a Linear Least-Squares Fit (Section III B of the paper).
+ * We define the following mathematical setup:
+ *
+ * wi (wx,wy,wx) -> are the gyroscope readings [RAD/s]
+ * xi            -> are the magnetometer readings [uT]
+ * xi_dot        -> is the derivative of xi
+ *
+ * Form here we can define:
+ *
+ *       yi = xi_dot + (wi x xi) (x denotes the cross product)
+ *
+ * and
+ *
+ *           /  0   -wz   wy \
+ *       Wi =|  wz   0    -wx |
+ *           \ -wy  wx     0  /
+ *
+ * which we can use to define, what we call first sum and second sum
+ *
+ *       firstSum = sum( Wi^2)
+ *
+ * and
+ *
+ *       secondSum = sum(Wi yi)
+ *
+ * The bias is calculated the following way
+ *
+ *       b = inverse(firstSum) * secondSum
+ *
+ * Notice that we use the diagonal of the matrix “inverse(firstSum)” as a
+ * confidence metric, to make a accuracy assessment.
+ *
+ * The algorithm expects time synchronized magnetometer and gyro data, any delay
+ * shift between those two greater than 5-10ms will result in poor accuracy.
+ * Furthermore, the minimum sample rate of the sensors should be higher than 50
+ * Hz.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_GYRO_CAL_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_GYRO_CAL_H_
+
+#ifdef SPHERE_FIT_ENABLED
+#ifndef DIVERSITY_CHECK_ENABLED
+#define DIVERSITY_CHECK_ENABLED
+#endif
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "calibration/magnetometer/mag_cal.h"
+#include "common/math/mat.h"
+#include "common/math/vec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Algo Batch Data Struct.
+struct MagGyroBatchCal {
+  // Batch Start time.
+  uint64_t start_time_ns;
+
+  // Time Stamp Buffer. Variable used to calculate 1/deltaT = odr_in_hz, where
+  // 1/deltaT is used to calculate the derivative of the mag signal.
+  uint64_t timestamp_buffer_ns;
+
+  // First sample in batch indicator.
+  bool first_sample;
+
+  // Matrix and vector definition.
+  struct Mat33 first_sum_update;
+  struct Vec3 last_mag_sample;
+  struct Vec3 second_sum_update;
+};
+
+// Algorithm tuning Parameter struct.
+struct MagGyroCalParameters {
+  // Batch window size.
+  uint64_t batch_window_in_ns;
+
+  // Algo resets after max_run_time.
+  uint64_t max_run_time_in_ns;
+
+  // Confidence Threshold.
+  // The confidence metric is explained in go/magcalp design document.
+  // Furthermore more details are given as well on how to tune it. Confidence
+  // Threshold high accuracy.
+  float confidence_threshold_high;
+
+  // Confidence Threshold medium accuracy.
+  float confidence_threshold_medium;
+
+  // Confidence Threshold start. Don't want to start with a bad matrix.
+  float confidence_threshold_start;
+};
+
+// Data Struct for the Batch average function.
+struct MagGyroAverageCal {
+  // Matrix and vector definition.
+  struct Mat33 first_sum_average;
+  struct Vec3 second_sum_average;
+
+  // Medium accuracy bias update.
+  bool medium_accuracy_bias_update;
+
+  // Confidence Metric.
+  struct Vec3 confidence_metric;
+
+  // Running biases update.
+  struct Vec3 bias_running;
+};
+
+// Main algo data Struct.
+struct MagGyroCal {
+  // Algorithm tuning parameters.
+  struct MagGyroCalParameters algo_parameters;
+
+  // Single batch struct. We use batches, which in turn get again averaged over
+  // time. The single batch is the amount of data that we do the Linear
+  // Least-Squares fit on and decide if we will add this to the averaging based
+  // on the confidence metric. Notice that we do on every batch a matrix
+  // inversion, hence in order to save power it is beneficial to make the batch
+  // size reasonably large. This is controlled by the “batch_window_in_ns”.
+  struct MagGyroBatchCal mgbc;
+
+  // Algo average struct.
+  struct MagGyroAverageCal mgac;
+
+  // New bias update.
+  float x_bias;
+  float y_bias;
+  float z_bias;
+};
+
+// Algorithm initialization.
+void magGyroCalInit(struct MagGyroCal *mgc,
+                    const struct MagGyroCalParameters *params);
+
+// Resets the batch struct.
+void magGyroCalReset(struct MagGyroCal *mgc);
+
+// Gets the current MagGyro bias estimate.
+void magGyroCalGetBias(struct MagGyroCal *mgc, float *x, float *y, float *z);
+
+// Main algorithm runner function.
+// sample_time_ns in [ns].
+// mag_x_ut, mag_y_ut, mag_z_ut are in [uT].
+// gyro_x_rps, gyro_y_rps, gyro_z_rps are in [RAD/s].
+// odr_hz is in [Hz].
+enum MagUpdate magGyroCalRun(struct MagGyroCal *mgc, uint64_t sample_time_ns,
+                             float mag_x_ut, float mag_y_ut, float mag_z_ut,
+                             float gyro_x_rps, float gyro_y_rps,
+                             float gyro_z_rps);
+
+//////// Function exposed for testing ///////////////////////////////////
+// Updating sensor data into the batch.
+// sample_time_ns in [ns].
+// mag_x_ut, mag_y_ut, mag_z_ut are in [uT].
+// gyro_x_rps, gyro_y_rps, gyro_z_rps are in [RAD/s].
+// odr_hz is in [Hz].
+void magGyroCalBatchUpdate(struct MagGyroBatchCal *mgbc,
+                           uint64_t sample_time_ns, float mag_x_ut,
+                           float mag_y_ut, float mag_z_ut, float gyro_x_rps,
+                           float gyro_y_rps, float gyro_z_rps);
+
+// Checks metrics and classifies bias update into either:
+// NO_UPDATE and no bias is updated
+// UPDATE_BIAS_MEDIUM bias is update with medium accuracy.
+// UPDATE_BIAS_HIGH bias is update with high accuracy.
+enum MagUpdate magGyroCalBiasUpdate(struct MagGyroCal *mgc,
+                                    struct Mat33 *first_sum_inverse);
+
+// Resetting the batch struct.
+void magGyroCalBatchReset(struct MagGyroBatchCal *mgbc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_GYRO_CAL_H_
diff --git a/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.c b/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.c
index eb23b87..3e23dcf 100644
--- a/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.c
+++ b/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.c
@@ -33,22 +33,16 @@
   memset(&mocs->sphere_data, 0, sizeof(mocs->sphere_data));
 }
 
-void initMagCalSphere(struct MagCalSphere *mocs, float x_bias, float y_bias,
-                      float z_bias, float c00, float c01, float c02, float c10,
-                      float c11, float c12, float c20, float c21, float c22,
-                      uint32_t min_batch_window_in_micros,
-                      size_t min_num_diverse_vectors,
-                      size_t max_num_max_distance, float var_threshold,
-                      float max_min_threshold, float local_field,
-                      float threshold_tuning_param,
-                      float max_distance_tuning_param) {
-  initMagCal(&mocs->moc, x_bias, y_bias, z_bias, c00, c01, c02, c10, c11, c12,
-             c20, c21, c22, min_batch_window_in_micros, min_num_diverse_vectors,
-             max_num_max_distance, var_threshold, max_min_threshold,
-             local_field, threshold_tuning_param, max_distance_tuning_param);
+void initMagCalSphere(
+    struct MagCalSphere *mocs,
+    const struct MagCalParameters *mag_cal_parameters,
+    const struct DiversityCheckerParameters *diverse_parameters,
+    float default_odr_in_hz) {
+  initMagCal(&mocs->moc, mag_cal_parameters, diverse_parameters);
   mocs->inv_data_size = 1.0f / (float)NUM_SPHERE_FIT_DATA;
   mocs->batch_time_in_sec =
-      (float)(min_batch_window_in_micros) * FROM_MICRO_SEC_TO_SEC;
+      (float)(mag_cal_parameters->min_batch_window_in_micros) *
+      FROM_MICRO_SEC_TO_SEC;
   // Initialize to take every sample, default setting.
   mocs->sample_drop = 0;
   magCalSphereReset(mocs);
@@ -63,6 +57,9 @@
   sphereFitSetSolverData(&mocs->sphere_fit.sphere_cal,
                          &mocs->sphere_fit.lm_data);
   calDataReset(&mocs->sphere_fit.sphere_param);
+
+  // Initializes the starting output data rate estimate.
+  magCalSphereOdrUpdate(mocs, default_odr_in_hz);
 }
 
 void magCalSphereDestroy(struct MagCalSphere *mocs) { (void)mocs; }
@@ -91,7 +88,7 @@
     if (mocs->number_of_data_samples < NUM_SPHERE_FIT_DATA) {
       memcpy(&mocs->sphere_data[mocs->number_of_data_samples *
                                 THREE_AXIS_DATA_DIM],
-             vec, sizeof(float) * 3);
+             vec, sizeof(float) * THREE_AXIS_DATA_DIM);
       // counting the numbers of samples in the data set.
       mocs->number_of_data_samples++;
     }
@@ -114,8 +111,7 @@
 
   // Running the sphere fit and checking if successful.
   if (sphereFitRunCal(&mocs->sphere_fit.sphere_cal, &data, sample_time_us)) {
-    // Updating Sphere parameters. Can use "calDataCorrectData" function to
-    // correct data.
+    // Updating sphere parameters.
     sphereFitGetLatestCal(&mocs->sphere_fit.sphere_cal,
                           &mocs->sphere_fit.sphere_param);
 
diff --git a/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.h b/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.h
index 85df48f..7c2fc86 100644
--- a/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.h
+++ b/firmware/os/algos/calibration/magnetometer/mag_sphere_fit.h
@@ -13,11 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_SPHERE_FIT_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_MAGNETOMETER_MAG_SPHERE_FIT_H_
 
-#include "calibration/common/sphere_fit_calibration.h"
 #include "calibration/magnetometer/mag_cal.h"
+#include "calibration/sphere_fit/sphere_fit_calibration.h"
 
 #define NUM_SPHERE_FIT_DATA 50
 
@@ -50,24 +51,17 @@
   float sphere_data[THREE_AXIS_DATA_DIM * NUM_SPHERE_FIT_DATA];
 };
 
-void initMagCalSphere(struct MagCalSphere *mocs,
-                      float x_bias, float y_bias, float z_bias,
-                      float c00, float c01, float c02, float c10, float c11,
-                      float c12, float c20, float c21, float c22,
-                      uint32_t min_batch_window_in_micros,
-                      size_t min_num_diverse_vectors,
-                      size_t max_num_max_distance,
-                      float var_threshold,
-                      float max_min_threshold,
-                      float local_field,
-                      float threshold_tuning_param,
-                      float max_distance_tuning_param);
+void initMagCalSphere(
+    struct MagCalSphere *mocs,
+    const struct MagCalParameters *mag_cal_parameters,
+    const struct DiversityCheckerParameters *diverse_parameters,
+    float default_odr_in_hz);
 
 void magCalSphereDestroy(struct MagCalSphere *mocs);
 
 enum MagUpdate magCalSphereUpdate(struct MagCalSphere *mocs,
-                                  uint64_t sample_time_us,
-                                  float x, float y, float z);
+                                  uint64_t sample_time_us, float x, float y,
+                                  float z);
 
 void magCalSphereOdrUpdate(struct MagCalSphere *mocs, float odr_in_hz);
 
diff --git a/firmware/os/algos/calibration/nano_calibration/aosp_nano_cal_parameters.h b/firmware/os/algos/calibration/nano_calibration/aosp_nano_cal_parameters.h
new file mode 100644
index 0000000..88557ad
--- /dev/null
+++ b/firmware/os/algos/calibration/nano_calibration/aosp_nano_cal_parameters.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_AOSP_NANO_CAL_PARAMETERS_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_AOSP_NANO_CAL_PARAMETERS_H_
+
+#ifdef ACCEL_CAL_ENABLED
+#include "calibration/accelerometer/accel_cal.h"
+#endif  // ACCEL_CAL_ENABLED
+
+#ifdef GYRO_CAL_ENABLED
+#include "calibration/gyroscope/gyro_cal.h"
+#include "calibration/over_temp/over_temp_cal.h"
+#endif  // GYRO_CAL_ENABLED
+
+#ifdef MAG_CAL_ENABLED
+#include "calibration/diversity_checker/diversity_checker.h"
+#include "calibration/magnetometer/mag_cal.h"
+#endif  // MAG_CAL_ENABLED
+
+#include "common/math/macros.h"
+
+namespace nano_calibration {
+
+//////// ACCELEROMETER CALIBRATION ////////
+#ifdef ACCEL_CAL_ENABLED
+constexpr AccelCalParameters kAccelCalParameters{
+    MSEC_TO_NANOS(800),  // t0
+    5,                   // n_s
+    15,                  // fx
+    15,                  // fxb
+    15,                  // fy
+    15,                  // fyb
+    15,                  // fz
+    15,                  // fzb
+    15,                  // fle
+    0.00025f             // th
+};
+#endif  // ACCEL_CAL_ENABLED
+
+//////// GYROSCOPE CALIBRATION ////////
+#ifdef GYRO_CAL_ENABLED
+constexpr GyroCalParameters kGyroCalParameters{
+    SEC_TO_NANOS(1.4),    // min_still_duration_nanos
+    SEC_TO_NANOS(1.4),    // max_still_duration_nanos [see, NOTE 1]
+    0,                    // calibration_time_nanos
+    MSEC_TO_NANOS(500),   // window_time_duration_nanos
+    0,                    // bias_x
+    0,                    // bias_y
+    0,                    // bias_z
+    0.95f,                // stillness_threshold
+    MDEG_TO_RAD * 60.0f,  // stillness_mean_delta_limit [rad/sec]
+    3.0e-5f,              // gyro_var_threshold [rad/sec]^2
+    3.0e-6f,              // gyro_confidence_delta [rad/sec]^2
+    4.5e-3f,              // accel_var_threshold [m/sec^2]^2
+    9.0e-4f,              // accel_confidence_delta [m/sec^2]^2
+    5.0f,                 // mag_var_threshold [uTesla]^2
+    1.0f,                 // mag_confidence_delta [uTesla]^2
+    1.5f,                 // temperature_delta_limit_celsius
+    true                  // gyro_calibration_enable
+};
+// [NOTE 1]: 'max_still_duration_nanos' is set to 1.4 seconds to achieve a max
+// stillness period of 1.5 seconds and avoid buffer boundary conditions that
+// could push the max stillness to the next multiple of the analysis window
+// length (i.e., 2.0 seconds).
+
+constexpr OverTempCalParameters kGyroOtcParameters{
+    MSEC_TO_NANOS(100),    // min_temp_update_period_nanos
+    DAYS_TO_NANOS(2),      // age_limit_nanos
+    0.75f,                 // delta_temp_per_bin
+    40.0f * MDEG_TO_RAD,   // jump_tolerance
+    100.0f * MDEG_TO_RAD,  // outlier_limit
+    250.0f * MDEG_TO_RAD,  // temp_sensitivity_limit
+    8.0e3f * MDEG_TO_RAD,  // sensor_intercept_limit
+    0.1f * MDEG_TO_RAD,    // significant_offset_change
+    5,                     // min_num_model_pts
+    true                   // over_temp_enable
+};
+#endif  // GYRO_CAL_ENABLED
+
+//////// MAGNETOMETER CALIBRATION ////////
+#ifdef MAG_CAL_ENABLED
+constexpr MagCalParameters kMagCalParameters{
+    3000000,  // min_batch_window_in_micros
+    0.0f,     // x_bias
+    0.0f,     // y_bias
+    0.0f,     // z_bias
+    1.0f,     // c00
+    0.0f,     // c01
+    0.0f,     // c02
+    0.0f,     // c10
+    1.0f,     // c11
+    0.0f,     // c12
+    0.0f,     // c20
+    0.0f,     // c21
+    1.0f      // c22
+};
+
+constexpr DiversityCheckerParameters kMagDiversityParameters{
+    6.0f,    // var_threshold
+    10.0f,   // max_min_threshold
+    48.0f,   // local_field
+    0.5f,    // threshold_tuning_param
+    2.552f,  // max_distance_tuning_param
+    8,       // min_num_diverse_vectors
+    1        // max_num_max_distance
+};
+#endif  // MAG_CAL_ENABLED
+
+}  // namespace nano_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_AOSP_NANO_CAL_PARAMETERS_H_
diff --git a/firmware/os/algos/calibration/nano_calibration/nano_calibration.cc b/firmware/os/algos/calibration/nano_calibration/nano_calibration.cc
new file mode 100644
index 0000000..68b477b
--- /dev/null
+++ b/firmware/os/algos/calibration/nano_calibration/nano_calibration.cc
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "calibration/nano_calibration/nano_calibration.h"
+
+#include <cstring>
+
+#include "chre/util/nanoapp/log.h"
+
+namespace nano_calibration {
+namespace {
+
+using ::online_calibration::CalibrationDataThreeAxis;
+using ::online_calibration::CalibrationTypeFlags;
+using ::online_calibration::SensorData;
+using ::online_calibration::SensorIndex;
+using ::online_calibration::SensorType;
+
+// NanoSensorCal logging macros.
+#ifdef NANO_SENSOR_CAL_DBG_ENABLED
+#ifndef LOG_TAG
+#define LOG_TAG "[ImuCal]"
+#endif
+#define NANO_CAL_LOGD(tag, format, ...) LOGD("%s " format, tag, ##__VA_ARGS__)
+#define NANO_CAL_LOGI(tag, format, ...) LOGI("%s " format, tag, ##__VA_ARGS__)
+#define NANO_CAL_LOGW(tag, format, ...) LOGW("%s " format, tag, ##__VA_ARGS__)
+#define NANO_CAL_LOGE(tag, format, ...) LOGE("%s " format, tag, ##__VA_ARGS__)
+#else
+#define NANO_CAL_LOGD(tag, format, ...) chreLogNull(format, ##__VA_ARGS__)
+#define NANO_CAL_LOGI(tag, format, ...) chreLogNull(format, ##__VA_ARGS__)
+#define NANO_CAL_LOGW(tag, format, ...) chreLogNull(format, ##__VA_ARGS__)
+#define NANO_CAL_LOGE(tag, format, ...) chreLogNull(format, ##__VA_ARGS__)
+#endif  // NANO_SENSOR_CAL_DBG_ENABLED
+
+}  // namespace
+
+void NanoSensorCal::Initialize(OnlineCalibrationThreeAxis *accel_cal,
+                               OnlineCalibrationThreeAxis *gyro_cal,
+                               OnlineCalibrationThreeAxis *mag_cal) {
+  // Loads stored calibration data and initializes the calibration algorithms.
+  accel_cal_ = accel_cal;
+  if (accel_cal_ != nullptr) {
+    if (accel_cal_->get_sensor_type() == SensorType::kAccelerometerMps2) {
+      if (LoadAshCalibration(CHRE_SENSOR_TYPE_ACCELEROMETER, accel_cal_,
+                             kAccelTag)) {
+        PrintCalibration(accel_cal_->GetSensorCalibration(),
+                         CalibrationTypeFlags::BIAS, kAccelTag);
+      }
+      NANO_CAL_LOGI(kAccelTag,
+                    "Accelerometer runtime calibration initialized.");
+    } else {
+      accel_cal_ = nullptr;
+      NANO_CAL_LOGE(kAccelTag, "Failed to initialize: wrong sensor type.");
+    }
+  }
+
+  gyro_cal_ = gyro_cal;
+  if (gyro_cal_ != nullptr) {
+    if (gyro_cal_->get_sensor_type() == SensorType::kGyroscopeRps) {
+      if (LoadAshCalibration(CHRE_SENSOR_TYPE_GYROSCOPE, gyro_cal_, kGyroTag)) {
+        PrintCalibration(
+            gyro_cal_->GetSensorCalibration(),
+            CalibrationTypeFlags::BIAS | CalibrationTypeFlags::OVER_TEMP,
+            kGyroTag);
+      }
+      NANO_CAL_LOGI(kGyroTag, "Gyroscope runtime calibration initialized.");
+    } else {
+      gyro_cal_ = nullptr;
+      NANO_CAL_LOGE(kGyroTag, "Failed to initialize: wrong sensor type.");
+    }
+  }
+
+  mag_cal_ = mag_cal;
+  if (mag_cal != nullptr) {
+    if (mag_cal->get_sensor_type() == SensorType::kMagnetometerUt) {
+      if (LoadAshCalibration(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, mag_cal_,
+                             kMagTag)) {
+        PrintCalibration(mag_cal_->GetSensorCalibration(),
+                         CalibrationTypeFlags::BIAS, kMagTag);
+      }
+      NANO_CAL_LOGI(kMagTag, "Magnetometer runtime calibration initialized.");
+    } else {
+      mag_cal_ = nullptr;
+      NANO_CAL_LOGE(kMagTag, "Failed to initialize: wrong sensor type.");
+    }
+  }
+}
+
+void NanoSensorCal::HandleSensorSamples(
+    uint16_t event_type, const chreSensorThreeAxisData *event_data) {
+  // Converts CHRE Event -> SensorData::SensorType.
+  SensorData sample;
+  switch (event_type) {
+    case CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA:
+      sample.type = SensorType::kAccelerometerMps2;
+      break;
+    case CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA:
+      sample.type = SensorType::kGyroscopeRps;
+      break;
+    case CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA:
+      sample.type = SensorType::kMagnetometerUt;
+      break;
+    default:
+      // This sensor type is not used.
+      NANO_CAL_LOGW("[NanoSensorCal]",
+                    "Unexpected 3-axis sensor type received.");
+      return;
+  }
+
+  // Sends the sensor payload to the calibration algorithms and checks for
+  // calibration updates.
+  const auto &header = event_data->header;
+  const auto *data = event_data->readings;
+  sample.timestamp_nanos = header.baseTimestamp;
+  for (size_t i = 0; i < header.readingCount; i++) {
+    sample.timestamp_nanos += data[i].timestampDelta;
+    memcpy(sample.data, data[i].v, sizeof(sample.data));
+    ProcessSample(sample);
+  }
+}
+
+void NanoSensorCal::HandleTemperatureSamples(
+    uint16_t event_type, const chreSensorFloatData *event_data) {
+  // Computes the mean of the batched temperature samples and delivers it to the
+  // calibration algorithms. Note, the temperature sensor batch size determines
+  // its minimum update interval.
+  if (event_type == CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA &&
+      event_data->header.readingCount > 0) {
+    const auto header = event_data->header;
+    const auto *data = event_data->readings;
+
+    SensorData sample;
+    sample.type = SensorType::kTemperatureCelsius;
+    sample.timestamp_nanos = header.baseTimestamp;
+
+    float accum_temperature_celsius = 0.0f;
+    for (size_t i = 0; i < header.readingCount; i++) {
+      sample.timestamp_nanos += data[i].timestampDelta;
+      accum_temperature_celsius += data[i].value;
+    }
+    sample.data[SensorIndex::kSingleAxis] =
+        accum_temperature_celsius / header.readingCount;
+    ProcessSample(sample);
+  } else {
+    NANO_CAL_LOGW("[NanoSensorCal]",
+                  "Unexpected single-axis sensor type received.");
+  }
+}
+
+void NanoSensorCal::ProcessSample(const SensorData &sample) {
+  // Sends a new sensor sample to each active calibration algorithm and sends
+  // out notifications for new calibration updates.
+  if (accel_cal_ != nullptr) {
+    const CalibrationTypeFlags accel_cal_flags =
+        accel_cal_->SetMeasurement(sample);
+    if (accel_cal_flags != CalibrationTypeFlags::NONE) {
+      NotifyAshCalibration(CHRE_SENSOR_TYPE_ACCELEROMETER,
+                           accel_cal_->GetSensorCalibration(), accel_cal_flags,
+                           kAccelTag);
+      PrintCalibration(accel_cal_->GetSensorCalibration(), accel_cal_flags,
+                       kAccelTag);
+    }
+  }
+
+  if (gyro_cal_ != nullptr) {
+    const CalibrationTypeFlags gyro_cal_flags =
+        gyro_cal_->SetMeasurement(sample);
+    if (gyro_cal_flags != CalibrationTypeFlags::NONE) {
+      if (NotifyAshCalibration(CHRE_SENSOR_TYPE_GYROSCOPE,
+                               gyro_cal_->GetSensorCalibration(),
+                               gyro_cal_flags, kGyroTag)) {
+        // Limits the log messaging update rate for the gyro calibrations since
+        // these can occur frequently with rapid temperature changes.
+        if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
+                sample.timestamp_nanos, gyro_notification_time_nanos_,
+                kNanoSensorCalMessageIntervalNanos)) {
+          gyro_notification_time_nanos_ = sample.timestamp_nanos;
+          PrintCalibration(gyro_cal_->GetSensorCalibration(), gyro_cal_flags,
+                           kGyroTag);
+        }
+      }
+    }
+  }
+
+  if (mag_cal_ != nullptr) {
+    const CalibrationTypeFlags mag_cal_flags = mag_cal_->SetMeasurement(sample);
+    if (mag_cal_flags != CalibrationTypeFlags::NONE) {
+      NotifyAshCalibration(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD,
+                           mag_cal_->GetSensorCalibration(), mag_cal_flags,
+                           kMagTag);
+      PrintCalibration(mag_cal_->GetSensorCalibration(), mag_cal_flags,
+                       kMagTag);
+    }
+  }
+}
+
+bool NanoSensorCal::NotifyAshCalibration(
+    uint8_t chreSensorType, const CalibrationDataThreeAxis &cal_data,
+    CalibrationTypeFlags flags, const char *sensor_tag) {
+  // Updates the sensor offset calibration using the ASH API.
+  ashCalInfo ash_cal_info;
+  memset(&ash_cal_info, 0, sizeof(ashCalInfo));
+  ash_cal_info.compMatrix[0] = 1.0f;  // Sets diagonal to unity (scale factor).
+  ash_cal_info.compMatrix[4] = 1.0f;
+  ash_cal_info.compMatrix[8] = 1.0f;
+  memcpy(ash_cal_info.bias, cal_data.offset, sizeof(ash_cal_info.bias));
+  ash_cal_info.accuracy = ASH_CAL_ACCURACY_HIGH;
+
+  if (!ashSetCalibration(chreSensorType, &ash_cal_info)) {
+    NANO_CAL_LOGE(sensor_tag, "ASH failed to apply calibration update.");
+    return false;
+  }
+
+  // Uses the ASH API to store ONLY the algorithm calibration parameters that
+  // have been modified by the calibration algorithm.
+  ashCalParams ash_cal_parameters;
+  if (flags & CalibrationTypeFlags::BIAS) {
+    ash_cal_parameters.offsetTempCelsius = cal_data.offset_temp_celsius;
+    memcpy(ash_cal_parameters.offset, cal_data.offset,
+           sizeof(ash_cal_parameters.offset));
+    ash_cal_parameters.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
+    ash_cal_parameters.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
+  }
+
+  if (flags & CalibrationTypeFlags::OVER_TEMP) {
+    memcpy(ash_cal_parameters.tempSensitivity, cal_data.temp_sensitivity,
+           sizeof(ash_cal_parameters.tempSensitivity));
+    memcpy(ash_cal_parameters.tempIntercept, cal_data.temp_intercept,
+           sizeof(ash_cal_parameters.tempIntercept));
+    ash_cal_parameters.tempSensitivitySource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
+    ash_cal_parameters.tempInterceptSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
+  }
+
+  if (!ashSaveCalibrationParams(chreSensorType, &ash_cal_parameters)) {
+    NANO_CAL_LOGE(sensor_tag, "ASH failed to write calibration update.");
+    return false;
+  }
+
+  return true;
+}
+
+bool NanoSensorCal::LoadAshCalibration(uint8_t chreSensorType,
+                                       OnlineCalibrationThreeAxis *online_cal,
+                                       const char *sensor_tag) {
+  ashCalParams recalled_ash_cal_parameters;
+  if (ashLoadCalibrationParams(chreSensorType, ASH_CAL_STORAGE_ASH,
+                               &recalled_ash_cal_parameters)) {
+    // Checks whether a valid set of runtime calibration parameters was received
+    // and can be used for initialization.
+    CalibrationTypeFlags flags = CalibrationTypeFlags::NONE;
+    if (DetectRuntimeCalibration(chreSensorType, sensor_tag, &flags,
+                                 &recalled_ash_cal_parameters)) {
+      CalibrationDataThreeAxis cal_data;
+      cal_data.type = online_cal->get_sensor_type();
+      cal_data.cal_update_time_nanos = chreGetTime();
+
+      // Analyzes the calibration flags and sets only the runtime calibration
+      // values that were received.
+      if (flags & CalibrationTypeFlags::BIAS) {
+        cal_data.offset_temp_celsius =
+            recalled_ash_cal_parameters.offsetTempCelsius;
+        memcpy(cal_data.offset, recalled_ash_cal_parameters.offset,
+               sizeof(cal_data.offset));
+      }
+
+      if (flags & CalibrationTypeFlags::OVER_TEMP) {
+        memcpy(cal_data.temp_sensitivity,
+               recalled_ash_cal_parameters.tempSensitivity,
+               sizeof(cal_data.temp_sensitivity));
+        memcpy(cal_data.temp_intercept,
+               recalled_ash_cal_parameters.tempIntercept,
+               sizeof(cal_data.temp_intercept));
+      }
+
+      // Sets the algorithm's initial calibration data and notifies ASH to apply
+      // the recalled calibration data.
+      if (online_cal->SetInitialCalibration(cal_data)) {
+        return NotifyAshCalibration(chreSensorType,
+                                    online_cal->GetSensorCalibration(), flags,
+                                    sensor_tag);
+      } else {
+        NANO_CAL_LOGE(sensor_tag,
+                      "Calibration data failed to initialize algorithm.");
+      }
+    }
+  } else {
+    NANO_CAL_LOGE(sensor_tag, "ASH failed to recall calibration data.");
+  }
+
+  return false;
+}
+
+bool NanoSensorCal::DetectRuntimeCalibration(uint8_t chreSensorType,
+                                             const char *sensor_tag,
+                                             CalibrationTypeFlags *flags,
+                                             ashCalParams *ash_cal_parameters) {
+  // Analyzes calibration source flags to determine whether factory calibration
+  // data was received. A valid factory calibration source will include at least
+  // an offset.
+  bool factory_cal_detected =
+      ash_cal_parameters->offsetSource == ASH_CAL_PARAMS_SOURCE_NONE &&
+      ash_cal_parameters->offsetTempCelsiusSource ==
+          ASH_CAL_PARAMS_SOURCE_FACTORY;
+
+  bool runtime_cal_detected = false;
+  if (factory_cal_detected) {
+    // Prints the retrieved factory calibration data.
+    NANO_CAL_LOGI(sensor_tag, "Factory calibration detected.");
+    PrintAshCalParams(*ash_cal_parameters, sensor_tag);
+
+    // Since the factory calibration is applied lower in the sensor framework,
+    // initialization using these parameters must be skipped to avoid the
+    // possibility of double calibration. Returns 'false' to avoid further
+    // initialization. Note, the runtime calibration algorithms dynamically
+    // correct the residuals that the factory calibration doesn't or can't
+    // account for (e.g., aging induced error charateristics).
+    return false;
+  } else {
+    // Analyzes calibration source flags to determine whether runtime
+    // calibration values have been loaded and may be used for initialization. A
+    // valid runtime calibration source will include at least an offset.
+
+    // Converts the ASH calibration source flags to CalibrationTypeFlags. These
+    // will be used to determine which values to copy from 'ash_cal_parameters'
+    // and provide to the calibration algorithms for initialization.
+    if (ash_cal_parameters->offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME &&
+        ash_cal_parameters->offsetTempCelsiusSource ==
+            ASH_CAL_PARAMS_SOURCE_RUNTIME) {
+      runtime_cal_detected = true;
+      *flags = CalibrationTypeFlags::BIAS;
+    }
+
+    if (ash_cal_parameters->tempSensitivitySource ==
+            ASH_CAL_PARAMS_SOURCE_RUNTIME &&
+        ash_cal_parameters->tempInterceptSource ==
+            ASH_CAL_PARAMS_SOURCE_RUNTIME) {
+      runtime_cal_detected = true;
+      *flags |= CalibrationTypeFlags::OVER_TEMP;
+    }
+
+    if (!runtime_cal_detected) {
+      // This is a warning (not an error) since the runtime algorithms will
+      // function correctly with no recalled calibration values. They will
+      // eventually trigger and update the system with valid calibration data.
+      NANO_CAL_LOGW(sensor_tag, "No runtime offset calibration data found.");
+    }
+  }
+
+  return runtime_cal_detected;
+}
+
+// Helper functions for logging calibration information.
+void NanoSensorCal::PrintAshCalParams(const ashCalParams &cal_params,
+                                      const char *sensor_tag) {
+  NANO_CAL_LOGI(sensor_tag, "Offset | Temperature [C]: %.6f, %.6f, %.6f | %.2f",
+                cal_params.offset[0], cal_params.offset[1],
+                cal_params.offset[2], cal_params.offsetTempCelsius);
+
+  NANO_CAL_LOGI(sensor_tag, "Temp Sensitivity [units/C]: %.6f, %.6f, %.6f",
+                cal_params.tempSensitivity[0], cal_params.tempSensitivity[1],
+                cal_params.tempSensitivity[2]);
+
+  NANO_CAL_LOGI(sensor_tag, "Temp Intercept [units]: %.6f, %.6f, %.6f",
+                cal_params.tempIntercept[0], cal_params.tempIntercept[1],
+                cal_params.tempIntercept[2]);
+
+  NANO_CAL_LOGI(sensor_tag, "Scale Factor: %.6f, %.6f, %.6f",
+                cal_params.scaleFactor[0], cal_params.scaleFactor[1],
+                cal_params.scaleFactor[2]);
+
+  NANO_CAL_LOGI(sensor_tag,
+                "Cross-Axis in [yx, zx, zy] order: %.6f, %.6f, %.6f",
+                cal_params.crossAxis[0], cal_params.crossAxis[1],
+                cal_params.crossAxis[2]);
+}
+
+void NanoSensorCal::PrintCalibration(const CalibrationDataThreeAxis &cal_data,
+                                     CalibrationTypeFlags flags,
+                                     const char *sensor_tag) {
+  if (flags & CalibrationTypeFlags::BIAS) {
+    NANO_CAL_LOGI(sensor_tag,
+                  "Offset | Temperature [C]: %.6f, %.6f, %.6f | %.2f",
+                  cal_data.offset[0], cal_data.offset[1], cal_data.offset[2],
+                  cal_data.offset_temp_celsius);
+  }
+
+  if (flags & CalibrationTypeFlags::OVER_TEMP) {
+    NANO_CAL_LOGI(sensor_tag, "Temp Sensitivity: %.6f, %.6f, %.6f",
+                  cal_data.temp_sensitivity[0], cal_data.temp_sensitivity[1],
+                  cal_data.temp_sensitivity[2]);
+    NANO_CAL_LOGI(sensor_tag, "Temp Intercept: %.6f, %.6f, %.6f",
+                  cal_data.temp_intercept[0], cal_data.temp_intercept[1],
+                  cal_data.temp_intercept[2]);
+  }
+}
+
+}  // namespace nano_calibration
diff --git a/firmware/os/algos/calibration/nano_calibration/nano_calibration.h b/firmware/os/algos/calibration/nano_calibration/nano_calibration.h
new file mode 100644
index 0000000..5985054
--- /dev/null
+++ b/firmware/os/algos/calibration/nano_calibration/nano_calibration.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This module provides a containing class (NanoSensorCal) for dynamic runtime
+ * calibration algorithms that affect the following sensors:
+ *       - Accelerometer (offset)
+ *       - Gyroscope (offset, with over-temperature compensation)
+ *       - Magnetometer (offset)
+ *
+ * Sensor Units:
+ *       - Accelerometer [meters/sec^2]
+ *       - Gyroscope     [radian/sec]
+ *       - Magnetometer  [micro Tesla, uT]
+ *       - Temperature   [Celsius].
+ *
+ * NOTE1: Define NANO_SENSOR_CAL_DBG_ENABLED to enable debug messaging.
+ *
+ * NOTE2: This module uses pointers to runtime calibration algorithm objects.
+ * These must be constructed and initialized outside of this class. The owner
+ * bares the burden of managing the lifetime of these objects with respect to
+ * the NanoSensorCal class which depends on these objects and handles their
+ * interaction with the Android ASH/CHRE system. This arrangement makes it
+ * convenient to modify the specific algorithm implementations (i.e., choice of
+ * calibration algorithm, parameter tuning, etc.) at the nanoapp level without
+ * the need to specialize the standard functionality implemented here.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_NANO_CALIBRATION_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_NANO_CALIBRATION_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <ash.h>
+#include <chre.h>
+
+#include "calibration/online_calibration/common_data/calibration_callback.h"
+#include "calibration/online_calibration/common_data/calibration_data.h"
+#include "calibration/online_calibration/common_data/online_calibration.h"
+#include "calibration/online_calibration/common_data/sensor_data.h"
+#include "common/math/macros.h"
+
+namespace nano_calibration {
+
+// Common log message sensor-specific identifiers.
+constexpr char kAccelTag[] = {"[NanoSensorCal:ACCEL_MPS2]"};
+constexpr char kGyroTag[] = {"[NanoSensorCal:GYRO_RPS]"};
+constexpr char kMagTag[] = {"[NanoSensorCal:MAG_UT]"};
+
+// Limits NanoSensorCal notifications to once every minute.
+constexpr uint64_t kNanoSensorCalMessageIntervalNanos = MIN_TO_NANOS(1);
+
+/*
+ * NanoSensorCal is a container class for dynamic runtime calibration sensor
+ * algorithms used by the IMU_Cal CHRE nanoapp. The main purpose of this class
+ * is to transfer sensor data to the sensor calibration algorithms and provide
+ * calibration updates to CHRE using the ASH API.
+ */
+class NanoSensorCal {
+ public:
+  // Alias used to reference the three-axis OnlineCalibration baseclass used by
+  // the runtime calibration sensor wrappers. This is for convenience and to
+  // help with code readability.
+  using OnlineCalibrationThreeAxis = online_calibration::OnlineCalibration<
+      online_calibration::CalibrationDataThreeAxis>;
+
+  NanoSensorCal() = default;
+
+  // Sets the sensor calibration object pointers and initializes the algorithms
+  // using factory or runtime values recalled using Android Sensor Hub (ASH). A
+  // nullptr may be passed in to disable a particular sensor calibration.
+  void Initialize(OnlineCalibrationThreeAxis *accel_cal,
+                  OnlineCalibrationThreeAxis *gyro_cal,
+                  OnlineCalibrationThreeAxis *mag_cal);
+
+  // Sends new sensor samples to the calibration algorithms.
+  void HandleSensorSamples(uint16_t event_type,
+                           const chreSensorThreeAxisData *event_data);
+
+  // Provides temperature updates to the calibration algorithms.
+  void HandleTemperatureSamples(uint16_t event_type,
+                                const chreSensorFloatData *event_data);
+
+ private:
+  // Passes sensor data to the runtime calibration algorithms.
+  void ProcessSample(const online_calibration::SensorData &sample);
+
+  // Loads factory and runtime calibration data using the Android Sensor Hub
+  // API. Returns 'true' when runtime calibration values were successfully
+  // recalled and used for algorithm initialization. 'sensor_tag' is a string
+  // that identifies a sensor-specific identifier for log meassages.
+  bool LoadAshCalibration(uint8_t chreSensorType,
+                          OnlineCalibrationThreeAxis *online_cal,
+                          const char *sensor_tag);
+
+  // Provides sensor calibration updates using the ASH API. Returns 'true' with
+  // a successful ASH update.
+  bool NotifyAshCalibration(
+      uint8_t chreSensorType,
+      const online_calibration::CalibrationDataThreeAxis &cal_data,
+      online_calibration::CalibrationTypeFlags flags, const char *sensor_tag);
+
+  // Checks whether 'ash_cal_parameters' is a valid set of runtime calibration
+  // data and can be used for algorithm initialization.
+  bool DetectRuntimeCalibration(uint8_t chreSensorType, const char *sensor_tag,
+                                online_calibration::CalibrationTypeFlags *flags,
+                                ashCalParams *ash_cal_parameters);
+
+  // Helper functions for logging calibration information.
+  void PrintAshCalParams(const ashCalParams &cal_params,
+                         const char *sensor_tag);
+
+  void PrintCalibration(
+      const online_calibration::CalibrationDataThreeAxis &cal_data,
+      online_calibration::CalibrationTypeFlags flags, const char *sensor_tag);
+
+  // Pointer to the accelerometer runtime calibration object.
+  OnlineCalibrationThreeAxis *accel_cal_ = nullptr;
+
+  // Pointer to the gyroscope runtime calibration object.
+  OnlineCalibrationThreeAxis *gyro_cal_ = nullptr;
+
+  // Limits the log messaging update rate for the gyro calibrations since these
+  // can occur frequently with rapid temperature changes.
+  uint64_t gyro_notification_time_nanos_ = 0;
+
+  // Pointer to the magnetometer runtime calibration object.
+  OnlineCalibrationThreeAxis *mag_cal_ = nullptr;
+};
+
+}  // namespace nano_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_NANO_CALIBRATION_NANO_CALIBRATION_H_
diff --git a/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.cc b/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.cc
new file mode 100644
index 0000000..3958ae8
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.cc
@@ -0,0 +1,71 @@
+#include "calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.h"
+
+#include "calibration/util/cal_log.h"
+
+namespace online_calibration {
+
+void AccelOffsetCal::Initialize(
+    const AccelCalParameters& accel_cal_parameters) {
+  accelCalInit(&accel_cal_, &accel_cal_parameters);
+  InitializeCalData();
+}
+
+CalibrationTypeFlags AccelOffsetCal::SetMeasurement(const SensorData& sample) {
+  // Routes the input sensor sample to the calibration algorithm.
+  switch (sample.type) {
+    case SensorType::kAccelerometerMps2:
+      accelCalRun(&accel_cal_, sample.timestamp_nanos,
+                  sample.data[SensorIndex::kXAxis],
+                  sample.data[SensorIndex::kYAxis],
+                  sample.data[SensorIndex::kZAxis],  // [m/sec^2]
+                  temperature_celsius_);
+
+#ifdef ACCEL_CAL_DBG_ENABLED
+      // Prints debug data report.
+      accelCalDebPrint(&accel_cal_, temperature_celsius_);
+#endif
+      break;
+
+    case SensorType::kTemperatureCelsius:
+      temperature_celsius_ = sample.data[SensorIndex::kSingleAxis];
+      break;
+
+    default:
+      // This sample is not required.
+      return cal_update_polling_flags_;
+  }
+
+  // Checks for a new offset estimate, and updates the calibration data.
+  if (accelCalNewBiasAvailable(&accel_cal_)) {
+    accelCalUpdateBias(&accel_cal_, &cal_data_.offset[0], &cal_data_.offset[1],
+                       &cal_data_.offset[2]);
+
+    cal_data_.offset_temp_celsius = temperature_celsius_;
+    cal_data_.cal_update_time_nanos = sample.timestamp_nanos;
+    cal_update_polling_flags_ = CalibrationTypeFlags::BIAS;
+    OnNotifyCalibrationUpdate(CalibrationTypeFlags::BIAS);
+  }
+
+  return cal_update_polling_flags_;
+}
+
+bool AccelOffsetCal::SetInitialCalibration(
+    const CalibrationDataThreeAxis& input_cal_data) {
+  // Checks that the input calibration type matches the algorithm type.
+  if (input_cal_data.type != get_sensor_type()) {
+    CAL_DEBUG_LOG("[AccelOffsetCal]",
+                  "SetInitialCalibration failed due to wrong sensor type.");
+    return false;
+  }
+
+  // Sets the accelerometer algorithm's calibration data.
+  accelCalBiasSet(&accel_cal_, input_cal_data.offset[0],
+                  input_cal_data.offset[1], input_cal_data.offset[2]);
+
+  // Sync's all initial calibration data.
+  cal_data_ = input_cal_data;
+
+  return true;
+}
+
+}  // namespace online_calibration
diff --git a/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.h b/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.h
new file mode 100644
index 0000000..d2ec494
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/accelerometer/accel_offset_cal/accel_offset_cal.h
@@ -0,0 +1,51 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_ACCELEROMETER_ACCEL_OFFSET_CAL_ACCEL_OFFSET_CAL_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_ACCELEROMETER_ACCEL_OFFSET_CAL_ACCEL_OFFSET_CAL_H_
+
+#include "calibration/accelerometer/accel_cal.h"
+#include "calibration/online_calibration/common_data/calibration_callback.h"
+#include "calibration/online_calibration/common_data/calibration_data.h"
+#include "calibration/online_calibration/common_data/online_calibration.h"
+#include "calibration/online_calibration/common_data/sensor_data.h"
+
+namespace online_calibration {
+
+/*
+ * This class is a wrapper for accelerometer offset calibration.
+ */
+class AccelOffsetCal final
+    : public OnlineCalibration<CalibrationDataThreeAxis> {
+ public:
+  // Default constructor.
+  AccelOffsetCal() = default;
+
+  // Creates an AccelOffsetCal with specified algorithm parameters.
+  explicit AccelOffsetCal(const AccelCalParameters& accel_cal_parameters) {
+    Initialize(accel_cal_parameters);
+  }
+
+  // Initializes with specified algorithm parameters, and resets the calibration
+  // data.
+  void Initialize(const AccelCalParameters& accel_cal_parameters);
+
+  // Sends new sensor data to the calibration algorithm, and returns the state
+  // of the calibration update flags, 'cal_update_polling_flags_'.
+  CalibrationTypeFlags SetMeasurement(const SensorData& sample) final;
+
+  // Sets the initial calibration data of the calibration algorithm. Returns
+  // true if set successfully.
+  bool SetInitialCalibration(
+      const CalibrationDataThreeAxis& input_cal_data) final;
+
+  // Returns the calibration sensor type.
+  SensorType get_sensor_type() const final {
+    return SensorType::kAccelerometerMps2;
+  };
+
+ private:
+  // Accelerometer offset calibration algorithm data structure.
+  AccelCal accel_cal_;
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_ACCELEROMETER_ACCEL_OFFSET_CAL_ACCEL_OFFSET_CAL_H_
diff --git a/firmware/os/algos/calibration/online_calibration/common_data/calibration_callback.h b/firmware/os/algos/calibration/online_calibration/common_data/calibration_callback.h
new file mode 100644
index 0000000..96c061f
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/common_data/calibration_callback.h
@@ -0,0 +1,68 @@
+/*
+ * This module provides the callback functionality employed by the online sensor
+ * calibration algorithms.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_CALLBACK_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_CALLBACK_H_
+
+#include "calibration/online_calibration/common_data/calibration_data.h"
+
+namespace online_calibration {
+
+/*
+ * This codebase avoids Standard Template Library (STL) containers to maximize
+ * its usefullness on embedded hardware with basic C++ compiler support. The
+ * following uses type erasure to implement callback functionality to a user's
+ * desired class method. The idea here is to store an object pointer by
+ * instantiating a class template (Callback) which implements a virtual
+ * interface function (CallbackInterface::Call).
+ *
+ * USAGE:
+ * See unit testing for a simple example of how to use this for callback
+ * functionality.
+ */
+
+// CalibrationType: Sets the calibration type (e.g., CalibrationDataThreeAxis).
+template <class CalibrationType>
+class CallbackInterface {
+ public:
+  // Interface function that is defined to implement the desired callback.
+  virtual void Call(const CalibrationType& cal_data,
+                    CalibrationTypeFlags cal_update_flags) = 0;
+  virtual ~CallbackInterface() {}
+};
+
+// ClassCallerType: Sets the object's class type for the callback.
+// CalibrationType: Sets the calibration type (e.g., CalibrationDataThreeAxis).
+template <class ClassCallerType, class CalibrationType>
+class Callback : public CallbackInterface<CalibrationType> {
+ public:
+  // Constructors.
+  Callback() = delete;
+  Callback(ClassCallerType* obj,
+           void (ClassCallerType::*func)(const CalibrationType& cal_data,
+                                         CalibrationTypeFlags cal_update_flags))
+      : object_ptr_(obj), function_ptr_(func) {}
+
+  // Implements the callback to the desired class-method.
+  void Call(const CalibrationType& cal_data,
+            CalibrationTypeFlags cal_update_flags) final {
+    (object_ptr_->*function_ptr_)(cal_data, cal_update_flags);
+  }
+
+ private:
+  // Pointer to the class that owns the callback method.
+  ClassCallerType* object_ptr_;
+
+  // Templated function pointer with the required function signature.
+  // Calibration callbacks must accept:
+  //   1. Constant reference to the calibration.
+  //   2. Bitmask indicating which calibration components have been updated.
+  void (ClassCallerType::*function_ptr_)(const CalibrationType& cal_data,
+                                         CalibrationTypeFlags cal_update_flags);
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_CALLBACK_H_
diff --git a/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.cc b/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.cc
new file mode 100644
index 0000000..23ebe4f
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.cc
@@ -0,0 +1,22 @@
+#include "calibration/online_calibration/common_data/calibration_data.h"
+
+namespace online_calibration {
+
+CalibrationTypeFlags operator|(CalibrationTypeFlags lhs,
+                               CalibrationTypeFlags rhs) {
+  return static_cast<CalibrationTypeFlags>(static_cast<char>(lhs) |
+                                           static_cast<char>(rhs));
+}
+
+bool operator&(CalibrationTypeFlags lhs, CalibrationTypeFlags rhs) {
+  return static_cast<char>(lhs) & static_cast<char>(rhs);
+}
+
+CalibrationTypeFlags& operator|=(CalibrationTypeFlags& lhs,
+                                 CalibrationTypeFlags rhs) {
+  lhs = static_cast<CalibrationTypeFlags>(static_cast<char>(lhs) |
+                                          static_cast<char>(rhs));
+  return lhs;
+}
+
+}  // namespace online_calibration
diff --git a/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.h b/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.h
new file mode 100644
index 0000000..8b95ef7
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/common_data/calibration_data.h
@@ -0,0 +1,196 @@
+/*
+ * This module provides the component definitions used to represent sensor
+ * calibration data, labeled flags/enumerators, and the callback functionality
+ * employed by the online sensor calibration algorithms.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_DATA_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_DATA_H_
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "calibration/online_calibration/common_data/sensor_data.h"
+#include "calibration/over_temp/over_temp_model.h"
+
+namespace online_calibration {
+
+/*
+ * Bitmask used to indicate which calibration values have changed for a given
+ * calibration update.
+ *
+ * [Bit Flag]   | [Affected Sensor Calibration Value]
+ * BIAS         - Quasi-static non-zero sensor output (offset), given conditions
+ *                where the sensor should ideally output zero. Includes
+ *                corrections for over-temperature compensation.
+ * SCALE_FACTOR - Sensor axis scaling (ideally unity).
+ * CROSS_AXIS   - Output sensitivity due to variations of perpendicular sensing
+ *                axes (ideally zero).
+ * OVER_TEMP    - Model parameters that capture variations in sensor behavior
+ *                with temperature (e.g., linear bias sensitivity model).
+ */
+enum class CalibrationTypeFlags : uint8_t {
+  NONE = 0x00,
+  BIAS = 0x01,
+  SCALE_FACTOR = 0x02,
+  CROSS_AXIS = 0x04,
+  OVER_TEMP = 0x08,
+  ALL = 0xFF
+};
+
+// Logic operators to assist with common bitmask setting/checking.
+CalibrationTypeFlags operator|(CalibrationTypeFlags lhs,
+                               CalibrationTypeFlags rhs);
+
+bool operator&(CalibrationTypeFlags lhs, CalibrationTypeFlags rhs);
+
+CalibrationTypeFlags& operator|=(CalibrationTypeFlags& lhs,
+                                 CalibrationTypeFlags rhs);
+
+/*
+ * Defines the calibration data specific to a prototypical three-axis sensing
+ * device (e.g., accelerometer, gyroscope, magnetometer).
+ *
+ * Calibration correction may be applied as:
+ *   corrected_data = scale_skew_matrix * (input_data - offset);
+ *
+ * 'offset' is the sensor bias estimate (with temperature compensation applied
+ * when supported by the underlying calibration algorithm).
+ *
+ * The 'scale_skew_matrix' is assumed to be in lower diagonal form where the
+ * sensor frame reference definition is such that cross-axis sensitivities
+ * cross_axis_xy, cross_axis_xz, and cross_axis_yz are set to zero.
+ *
+ *   scale_skew_matrix = [scale_factor_x    0                 0
+ *                        cross_axis_yx     scale_factor_y    0
+ *                        cross_axis_zx     cross_axis_zy     scale_factor_z]
+ *
+ * NOTE1: If over-temperature compensation is provided, then temperature
+ * compensation is already applied to the 'offset'. Coefficients representative
+ * of the sensor's temperature dependency model are provided, and may be used
+ * for model initialization after a system restart.
+ *
+ *   temp_sensitivity - Modeled temperature sensitivity (i.e., linear slope).
+ *   temp_intercept   - Linear model intercept.
+ *
+ * The model equation for the over-temperature compensated offset:
+ *   compensated_offset = temp_sensitivity * current_temp + temp_intercept
+ *
+ * NOTE2: Unless otherwise stated, 3-dimensional array indices: 0=x, 1=y, 2=z.
+ */
+struct CalibrationDataThreeAxis {
+  // Timestamp for the most recent calibration update.
+  uint64_t cal_update_time_nanos = 0;
+
+  // The sensor's offset (i.e., bias) in the x-, y-, z-axes at
+  // 'offset_temp_celsius'.
+  float offset[3];
+
+  // The temperature associated with the sensor offset.
+  float offset_temp_celsius;
+
+  // The temperature sensitivity of the offset.
+  float temp_sensitivity[3];  // [sensor_units/Celsius]
+
+  // The sensor's offset at zero degrees Celsius.
+  float temp_intercept[3];  // [sensor_units]
+
+  // The sensor's scale factor for each axis.
+  float scale_factor[3];
+
+  // The cross-axis factors in order: [0=yx, 1=zx, 2=zy].
+  float cross_axis[3];
+
+  // Indicates the type of sensing device being calibrated.
+  SensorType type = SensorType::kUndefined;
+
+  // Optional pointer to an array of over-temperature model data (null when not
+  // used). For initialization, populating a model dataset will take precendence
+  // over the linear model parameters provided in the calibration data.
+  OverTempModelThreeAxis* otc_model_data = nullptr;
+  int16_t num_model_pts = 0;
+
+  // Helper function that resets the calibration data to a set of neutral
+  // reference values where no calibration correction would be applied if used.
+  void reset() {
+    otc_model_data = nullptr;
+    offset_temp_celsius = 0.0f;
+    scale_factor[0] = 1.0f;
+    scale_factor[1] = 1.0f;
+    scale_factor[2] = 1.0f;
+    memset(offset, 0, sizeof(offset));
+    memset(temp_sensitivity, 0, sizeof(temp_sensitivity));
+    memset(temp_intercept, 0, sizeof(temp_intercept));
+    memset(cross_axis, 0, sizeof(cross_axis));
+  }
+
+  CalibrationDataThreeAxis() { reset(); }
+};
+
+/*
+ * Defines the calibration data for single dimensional sensing device (e.g.,
+ * thermometer, barometer).
+ *
+ * Calibration correction may be applied as:
+ *   corrected_data = scale_factor * (input_data - offset);
+ *
+ * 'offset' is the sensor bias estimate (with temperature compensation applied,
+ * if supported by the underlying calibration algorithm).
+ *
+ * NOTE: If over-temperature compensation is provided, then temperature
+ * compensation is already applied to the 'offset'. Coefficients representative
+ * of the sensor's temperature dependency model are provided, and may be used
+ * for model initialization after a system restart.
+ *
+ *   temp_sensitivity - Modeled temperature sensitivity (i.e., linear slope).
+ *   temp_intercept   - Linear model intercept.
+ *
+ * The model equation for the over-temperature compensated offset:
+ *   compensated_offset = temp_sensitivity * current_temp + temp_intercept
+ */
+struct CalibrationDataSingleAxis {
+  // Timestamp for the most recent calibration update.
+  uint64_t cal_update_time_nanos = 0;
+
+  // The sensor's offset (i.e., bias) at temperature, 'offset_temp_celsius'.
+  float offset;
+
+  // The temperature associated with the sensor offset.
+  float offset_temp_celsius;
+
+  // The temperature sensitivity of the offset.
+  float temp_sensitivity;  // [sensor_units/Celsius]
+
+  // The sensor's offset at zero degrees Celsius.
+  float temp_intercept;  // [sensor_units]
+
+  // The sensor's scale factor.
+  float scale_factor;
+
+  // Indicates the type of sensing device being calibrated.
+  SensorType type = SensorType::kUndefined;
+
+  // Optional pointer to an array of over-temperature model data (null when not
+  // used). For initialization, populating a model dataset will take precendence
+  // over the linear model parameters provided in the calibration data.
+  OverTempModelSingleAxis* otc_model_data = nullptr;
+  int16_t num_model_pts = 0;
+
+  // Helper function that resets the calibration data to a set of neutral
+  // reference values where no calibration correction would be applied if used.
+  void reset() {
+    otc_model_data = nullptr;
+    scale_factor = 1.0f;
+    offset_temp_celsius = 0.0f;
+    offset = 0.0f;
+    temp_sensitivity = 0.0f;
+    temp_intercept = 0.0f;
+  }
+
+  CalibrationDataSingleAxis() { reset(); }
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_CALIBRATION_DATA_H_
diff --git a/firmware/os/algos/calibration/online_calibration/common_data/online_calibration.h b/firmware/os/algos/calibration/online_calibration/common_data/online_calibration.h
new file mode 100644
index 0000000..bf348ac
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/common_data/online_calibration.h
@@ -0,0 +1,128 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_ONLINE_CALIBRATION_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_ONLINE_CALIBRATION_H_
+
+#include <string.h>
+
+#include "calibration/online_calibration/common_data/calibration_callback.h"
+#include "calibration/online_calibration/common_data/calibration_data.h"
+#include "calibration/online_calibration/common_data/sensor_data.h"
+
+namespace online_calibration {
+
+/*
+ * This abstract base class provides a set of general interface functions for
+ * calibration algorithms. The data structures used are intended to be lean and
+ * portable to a wide variety of software and hardware systems. Algorithm
+ * wrappers may use this as a basis for providing the following functionality:
+ *
+ *   SetMeasurement - Delivers new sensor data.
+ *   SetInitialCalibration - Initializes the algorithm's calibration data.
+ *   GetSensorCalibration - Retrieves the latest calibration data set.
+ *   new_calibration_ready - Used to poll for new calibration updates.
+ *   SetCalibrationCallback - User provides a pointer its own Callback object.
+ *   UpdateDynamicSystemSettings - Provides feedback to adjust system behavior.
+ *   get_sensor_type - Returns the sensor type which is being calibrated.
+ *
+ * NOTE 1: This class accomodates two methods of providing calibration updates.
+ * Either, or both, may be used depending on system requirements. 1) Polling can
+ * be achieved with new_calibration_ready/GetSensorCalibration functions. 2)
+ * Callback notification of new calibration updates can managed using the
+ * SetCalibrationCallback function.
+ *
+ * NOTE 2: This code implementation specifically avoids using standard template
+ * libraries (STL) and other external API’s since they may not be fully
+ * supported on embedded hardware targets. Only basic C/C++ support will be
+ * assumed.
+ */
+
+// CalibrationType: Sets the calibration type (e.g., CalibrationDataThreeAxis).
+template <class CalibrationType>
+class OnlineCalibration {
+ public:
+  // Virtual destructor.
+  virtual ~OnlineCalibration() {}
+
+  // Sends new sensor data to the calibration algorithm, and returns the state
+  // of the calibration update flags, 'cal_update_polling_flags_'.
+  virtual CalibrationTypeFlags SetMeasurement(const SensorData& sample) = 0;
+
+  // Sets the initial calibration data of the calibration algorithm. Returns
+  // "true" if set successfully.
+  virtual bool SetInitialCalibration(const CalibrationType& cal_data) = 0;
+
+  // Polling Updates: New calibration updates are generated during
+  // SetMeasurement and the 'cal_update_polling_flags_' are set according to
+  // which calibration values have changed. To prevent missing updates in
+  // systems that use polling, this bitmask remains latched until the
+  // calibration data is retrieved with this function.
+  const CalibrationType& GetSensorCalibration() const {
+    cal_update_polling_flags_ = CalibrationTypeFlags::NONE;
+    return cal_data_;
+  }
+
+  // Polling Updates: This function returns 'cal_update_polling_flags_' to
+  // indicate which calibration components have a pending update. The updated
+  // calibration data may be retrieved with GetSensorCalibration, and the
+  // 'cal_update_polling_flags_' will reset.
+  CalibrationTypeFlags new_calibration_ready() const {
+    return cal_update_polling_flags_;
+  }
+
+  // Sets the pointer to the CallbackInterface object used for notification of
+  // new calibration updates.
+  void SetCalibrationCallback(
+      CallbackInterface<CalibrationType>* calibration_callback) {
+    calibration_callback_ = calibration_callback;
+  }
+
+  // This provides operational feedback to the system based on status such as
+  // active sensors and sampling rates. Feedback may include adjusting
+  // system-level settings including sensor sampling rate, latency,
+  // active/passive subscription, etc. Returns "true" if a system setting is
+  // requested to change.
+  // TODO(davejacobs) -- Determine implementation details.
+  virtual bool UpdateDynamicSystemSettings(
+      /*System Parameters In, System Feedback Out*/) const {
+    return false;
+  }
+
+  // Returns the sensor-type this calibration algorithm provides updates for.
+  virtual SensorType get_sensor_type() const = 0;
+
+ protected:
+  // Helper function that activates the registered callback.
+  void OnNotifyCalibrationUpdate(CalibrationTypeFlags cal_update_flags) const {
+    if (calibration_callback_ != nullptr) {
+      calibration_callback_->Call(cal_data_, cal_update_flags);
+    }
+  }
+
+  // Helper function used to initialize the calibration data.
+  void InitializeCalData() {
+    cal_data_.reset();
+    cal_data_.type = get_sensor_type();
+    cal_update_polling_flags_ = CalibrationTypeFlags::NONE;
+  }
+
+  // Stores the sensor calibration data.
+  CalibrationType cal_data_;
+
+  // Tracks the most recent sensor temperature value.
+  float temperature_celsius_ = kInvalidTemperatureCelsius;
+
+  // This bitmask indicates which subset of calibration parameters have changed
+  // and is used specifically for polling; the callback notification passes its
+  // own set of update flags which do not need this latching behavior. Marked
+  // mutable so that these flags may be reset when GetSensorCalibration is
+  // called.
+  mutable CalibrationTypeFlags cal_update_polling_flags_ =
+      CalibrationTypeFlags::NONE;
+
+ private:
+  // Pointer to a callback object.
+  CallbackInterface<CalibrationType>* calibration_callback_ = nullptr;
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_ONLINE_CALIBRATION_H_
diff --git a/firmware/os/algos/calibration/online_calibration/common_data/sensor_data.h b/firmware/os/algos/calibration/online_calibration/common_data/sensor_data.h
new file mode 100644
index 0000000..9849ad2
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/common_data/sensor_data.h
@@ -0,0 +1,83 @@
+/*
+ * This module provides the component definitions used to represent sensor
+ * data employed by the online sensor calibration algorithms.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_SENSOR_DATA_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_SENSOR_DATA_H_
+
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+namespace online_calibration {
+
+// Defines an invalid or uninitialized temperature value.
+constexpr float kInvalidTemperatureCelsius = -274.0f;
+
+// Unit conversion from nanoseconds to microseconds.
+constexpr uint64_t NanoToMicroseconds(uint64_t x) { return x / 1000; }
+
+// Identifies the various sensing devices used by the calibration algorithms.
+enum class SensorType : int8_t {
+  kUndefined = 0,
+  kAccelerometerMps2 = 1,   // 3-axis sensor (units = meter/sec^2).
+  kGyroscopeRps = 2,        // 3-axis sensor (units = radian/sec).
+  kMagnetometerUt = 3,      // 3-axis sensor (units = micro-Tesla).
+  kTemperatureCelsius = 4,  // 1-axis sensor (units = degrees Celsius).
+  kBarometerHpa = 5,        // 1-axis sensor (units = hecto-Pascal).
+  kWifiM = 6                // 3-axis sensor (units = meter).
+};
+
+/*
+ * SensorData is a generalized data structure used to represent sensor samples
+ * produced by either a single- or three-axis device. Usage is implied through
+ * the sensor type (i.e., Gyroscope is a three-axis sensor and would therefore
+ * use all elements of 'data'; a pressure sensor is single-dimensional and would
+ * use 'data[SensorIndex::kSingleAxis]'). This arbitration is determined
+ * at the algorithm wrapper level where knowledge of a sensor's dimensionality
+ * is clearly understood.
+ *
+ * NOTE: The unified dimensional representation makes it convenient to pass
+ * either type of data into the interface functions defined in the
+ * OnlineCalibration.
+ */
+
+// Axis index definitions for SensorData::data.
+enum SensorIndex : int8_t {
+  kSingleAxis = 0,
+  kXAxis = kSingleAxis,
+  kYAxis = 1,
+  kZAxis = 2,
+};
+
+struct SensorData {
+  // Indicates the type of sensor this data originated from.
+  SensorType type;
+
+  // Sensor sample timestamp.
+  uint64_t timestamp_nanos;
+
+  // Generalized sensor sample (represents either single- or three-axis data).
+  float data[3];
+
+  SensorData() : type(SensorType::kUndefined), timestamp_nanos(0) {
+    memset(data, 0, sizeof(data));
+  }
+
+  SensorData(SensorType type, uint64_t ts, float x, float y, float z)
+      : type(type), timestamp_nanos(ts) {
+    data[SensorIndex::kXAxis] = x;
+    data[SensorIndex::kYAxis] = y;
+    data[SensorIndex::kZAxis] = z;
+  }
+
+  SensorData(SensorType type, uint64_t ts, float single_axis_sample)
+      : type(type), timestamp_nanos(ts) {
+    data[SensorIndex::kSingleAxis] = single_axis_sample;
+  }
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_COMMON_DATA_SENSOR_DATA_H_
diff --git a/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.cc b/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.cc
new file mode 100644
index 0000000..67ea497
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.cc
@@ -0,0 +1,150 @@
+#include "calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.h"
+
+#include "calibration/util/cal_log.h"
+
+namespace online_calibration {
+
+void GyroOffsetOtcCal::Initialize(const GyroCalParameters& gyro_cal_parameters,
+                                  const OverTempCalParameters& otc_parameters) {
+  gyroCalInit(&gyro_cal_, &gyro_cal_parameters);
+  overTempCalInit(&over_temp_cal_, &otc_parameters);
+  InitializeCalData();
+}
+
+CalibrationTypeFlags GyroOffsetOtcCal::SetMeasurement(
+    const SensorData& sample) {
+  // Routes the input sensor sample to the calibration algorithm.
+  switch (sample.type) {
+    case SensorType::kAccelerometerMps2:
+      gyroCalUpdateAccel(&gyro_cal_, sample.timestamp_nanos,
+                         sample.data[SensorIndex::kXAxis],
+                         sample.data[SensorIndex::kYAxis],
+                         sample.data[SensorIndex::kZAxis]);  // [m/sec^2]
+      break;
+
+    case SensorType::kGyroscopeRps:
+      gyroCalUpdateGyro(&gyro_cal_, sample.timestamp_nanos,
+                        sample.data[SensorIndex::kXAxis],
+                        sample.data[SensorIndex::kYAxis],
+                        sample.data[SensorIndex::kZAxis],  // [rad/sec]
+                        temperature_celsius_);
+      break;
+
+    case SensorType::kMagnetometerUt:
+      gyroCalUpdateMag(&gyro_cal_, sample.timestamp_nanos,
+                       sample.data[SensorIndex::kXAxis],
+                       sample.data[SensorIndex::kYAxis],
+                       sample.data[SensorIndex::kZAxis]);  // [micro-Tesla]
+      break;
+
+    case SensorType::kTemperatureCelsius:
+      temperature_celsius_ = sample.data[SensorIndex::kSingleAxis];
+      overTempCalSetTemperature(&over_temp_cal_, sample.timestamp_nanos,
+                                temperature_celsius_);
+      break;
+
+    default:
+      // This sample is not required.
+      return cal_update_polling_flags_;
+  }
+
+  // Checks for a new calibration, and updates the OTC.
+  if (gyroCalNewBiasAvailable(&gyro_cal_)) {
+    float offset[3];
+    float temperature_celsius = kInvalidTemperatureCelsius;
+    uint64_t calibration_time_nanos = 0;
+    gyroCalGetBias(&gyro_cal_, &offset[0], &offset[1], &offset[2],
+                   &temperature_celsius, &calibration_time_nanos);
+    overTempCalUpdateSensorEstimate(&over_temp_cal_, calibration_time_nanos,
+                                    offset, temperature_celsius);
+  }
+
+  // Checks the OTC for a new calibration model update.
+  const bool new_otc_model_update =
+      overTempCalNewModelUpdateAvailable(&over_temp_cal_);
+
+  // Checks for a change in the temperature compensated offset estimate.
+  const bool new_otc_offset = overTempCalNewOffsetAvailable(&over_temp_cal_);
+
+  // Sets the new calibration data.
+  CalibrationTypeFlags cal_update_callback_flags = CalibrationTypeFlags::NONE;
+  if (new_otc_offset) {
+    overTempCalGetOffset(&over_temp_cal_, &cal_data_.offset_temp_celsius,
+                         cal_data_.offset);
+    cal_data_.cal_update_time_nanos = sample.timestamp_nanos;
+    cal_update_callback_flags |= CalibrationTypeFlags::BIAS;
+  }
+
+  if (new_otc_model_update) {
+    // Sets the pointer to the OTC model dataset and the number of model points.
+    cal_data_.otc_model_data = over_temp_cal_.model_data;
+    cal_data_.num_model_pts = over_temp_cal_.num_model_pts;
+
+    cal_update_callback_flags |= CalibrationTypeFlags::OVER_TEMP;
+    overTempCalGetModel(&over_temp_cal_, cal_data_.offset,
+                        &cal_data_.offset_temp_celsius,
+                        &cal_data_.cal_update_time_nanos,
+                        cal_data_.temp_sensitivity, cal_data_.temp_intercept);
+  }
+
+  // Sets the new-calibration polling flag, and notifies a calibration callback
+  // listener of the new update.
+  if (new_otc_model_update || new_otc_offset) {
+    cal_update_polling_flags_ |= cal_update_callback_flags;
+    OnNotifyCalibrationUpdate(cal_update_callback_flags);
+  }
+
+  // Print debug data reports.
+#ifdef GYRO_CAL_DBG_ENABLED
+  gyroCalDebugPrint(&gyro_cal_, sample.timestamp_nanos);
+#endif  // GYRO_CAL_DBG_ENABLED
+#ifdef OVERTEMPCAL_DBG_ENABLED
+  overTempCalDebugPrint(&over_temp_cal_, sample.timestamp_nanos);
+#endif  // OVERTEMPCAL_DBG_ENABLED
+
+  return cal_update_polling_flags_;
+}
+
+bool GyroOffsetOtcCal::SetInitialCalibration(
+    const CalibrationDataThreeAxis& input_cal_data) {
+  // Checks that the input calibration type matches the algorithm type.
+  if (input_cal_data.type != get_sensor_type()) {
+    CAL_DEBUG_LOG("[GyroOffsetOtcCal]",
+                  "SetInitialCalibration failed due to wrong sensor type.");
+    return false;
+  }
+
+  // Sync's all initial calibration data.
+  cal_data_ = input_cal_data;
+
+  // Sets the initial calibration data (offset and OTC model parameters).
+  gyroCalSetBias(&gyro_cal_, cal_data_.offset[0], cal_data_.offset[1],
+                 cal_data_.offset[2], cal_data_.offset_temp_celsius,
+                 cal_data_.cal_update_time_nanos);
+
+  overTempCalSetModel(&over_temp_cal_, cal_data_.offset,
+                      cal_data_.offset_temp_celsius,
+                      cal_data_.cal_update_time_nanos,
+                      cal_data_.temp_sensitivity, cal_data_.temp_intercept,
+                      /*jump_start_model=*/false);
+
+  const bool load_new_model_dataset =
+      (input_cal_data.otc_model_data != nullptr &&
+       input_cal_data.num_model_pts > 0);
+
+  if (load_new_model_dataset) {
+    // Loads the new model dataset and uses it to update the linear model
+    // parameters.
+    overTempCalSetModelData(&over_temp_cal_, input_cal_data.num_model_pts,
+                            cal_data_.cal_update_time_nanos,
+                            input_cal_data.otc_model_data);
+  }
+
+  // Sets the pointer to the OTC model dataset and the number of model points.
+  cal_data_.otc_model_data = over_temp_cal_.model_data;
+  cal_data_.num_model_pts = over_temp_cal_.num_model_pts;
+
+  return true;
+}
+
+}  // namespace online_calibration
diff --git a/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.h b/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.h
new file mode 100644
index 0000000..74ba982
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/gyroscope/gyro_offset_over_temp_cal/gyro_offset_over_temp_cal.h
@@ -0,0 +1,58 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_GYROSCOPE_GYRO_OFFSET_OVER_TEMP_CAL_GYRO_OFFSET_OVER_TEMP_CAL_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_GYROSCOPE_GYRO_OFFSET_OVER_TEMP_CAL_GYRO_OFFSET_OVER_TEMP_CAL_H_
+
+#include "calibration/gyroscope/gyro_cal.h"
+#include "calibration/online_calibration/common_data/calibration_callback.h"
+#include "calibration/online_calibration/common_data/calibration_data.h"
+#include "calibration/online_calibration/common_data/online_calibration.h"
+#include "calibration/online_calibration/common_data/sensor_data.h"
+#include "calibration/over_temp/over_temp_cal.h"
+
+namespace online_calibration {
+
+/*
+ * This class is a wrapper for the gyroscope offset calibration with
+ * over-temperature compensation (OTC).
+ */
+class GyroOffsetOtcCal final
+    : public OnlineCalibration<CalibrationDataThreeAxis> {
+ public:
+  // Default constructor.
+  GyroOffsetOtcCal() = default;
+
+  // Creates an GyroOffsetOtcCal with specified algorithm parameters.
+  GyroOffsetOtcCal(const GyroCalParameters& gyro_cal_parameters,
+                   const OverTempCalParameters& otc_parameters) {
+    Initialize(gyro_cal_parameters, otc_parameters);
+  }
+
+  // Initializes with specified algorithm parameters, and resets the calibration
+  // data.
+  void Initialize(const GyroCalParameters& gyro_cal_parameters,
+                  const OverTempCalParameters& otc_parameters);
+
+  // Sends new sensor data to the calibration algorithm, and returns the state
+  // of the calibration update flags, 'cal_update_polling_flags_'.
+  CalibrationTypeFlags SetMeasurement(const SensorData& sample) final;
+
+  // Sets the initial calibration data of the calibration algorithm. Returns
+  // true if set successfully.
+  bool SetInitialCalibration(
+      const CalibrationDataThreeAxis& input_cal_data) final;
+
+  // Returns the calibration sensor type.
+  SensorType get_sensor_type() const final {
+    return SensorType::kGyroscopeRps;
+  };
+
+ private:
+  // GyroCal algorithm data structure.
+  GyroCal gyro_cal_;
+
+  // Over-temperature offset compensation algorithm data structure.
+  OverTempCal over_temp_cal_;
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_GYROSCOPE_GYRO_OFFSET_OVER_TEMP_CAL_GYRO_OFFSET_OVER_TEMP_CAL_H_
diff --git a/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.cc b/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.cc
new file mode 100644
index 0000000..a963dcd
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.cc
@@ -0,0 +1,68 @@
+#include "calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.h"
+
+#include "calibration/util/cal_log.h"
+
+namespace online_calibration {
+
+void MagDiverseCal::Initialize(
+    const MagCalParameters& mag_cal_parameters,
+    const DiversityCheckerParameters& diversity_parameters) {
+  initMagCal(&mag_cal_, &mag_cal_parameters, &diversity_parameters);
+  InitializeCalData();
+}
+
+CalibrationTypeFlags MagDiverseCal::SetMeasurement(const SensorData& sample) {
+  // Routes the input sensor sample to the calibration algorithm.
+  MagUpdate new_calibration_update = MagUpdate::NO_UPDATE;
+  switch (sample.type) {
+    case SensorType::kMagnetometerUt:
+      new_calibration_update = magCalUpdate(
+          &mag_cal_, NanoToMicroseconds(sample.timestamp_nanos),
+          sample.data[SensorIndex::kXAxis], sample.data[SensorIndex::kYAxis],
+          sample.data[SensorIndex::kZAxis]);
+      break;
+
+    case SensorType::kTemperatureCelsius:
+      temperature_celsius_ = sample.data[SensorIndex::kSingleAxis];
+      break;
+
+    default:
+      // This sample is not required.
+      return cal_update_polling_flags_;
+  }
+
+  // Checks for a new offset estimate, and updates the calibration data.
+  if (MagUpdate::UPDATE_BIAS & new_calibration_update) {
+    magCalGetBias(&mag_cal_, &cal_data_.offset[0], &cal_data_.offset[1],
+                  &cal_data_.offset[2]);
+
+    cal_data_.offset_temp_celsius = temperature_celsius_;
+    cal_data_.cal_update_time_nanos = sample.timestamp_nanos;
+    cal_update_polling_flags_ = CalibrationTypeFlags::BIAS;
+    OnNotifyCalibrationUpdate(CalibrationTypeFlags::BIAS);
+  }
+
+  return cal_update_polling_flags_;
+}
+
+bool MagDiverseCal::SetInitialCalibration(
+    const CalibrationDataThreeAxis& input_cal_data) {
+  // Checks that the input calibration type matches the algorithm type.
+  if (input_cal_data.type != get_sensor_type()) {
+    CAL_DEBUG_LOG("[MagDiverseCal]",
+                  "SetInitialCalibration failed due to wrong sensor type.");
+    return false;
+  }
+
+  // Sets the magnetometer algorithm's calibration data.
+  magCalReset(&mag_cal_);  // Resets the magnetometer's offset vector.
+  magCalAddBias(&mag_cal_, input_cal_data.offset[0], input_cal_data.offset[1],
+                input_cal_data.offset[2]);
+
+  // Sync's all initial calibration data.
+  cal_data_ = input_cal_data;
+
+  return true;
+}
+
+}  // namespace online_calibration
diff --git a/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.h b/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.h
new file mode 100644
index 0000000..23ac3c4
--- /dev/null
+++ b/firmware/os/algos/calibration/online_calibration/magnetometer/mag_diverse_cal/mag_diverse_cal.h
@@ -0,0 +1,52 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_MAGNETOMETER_MAG_DIVERSE_CAL_MAG_DIVERSE_CAL_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_MAGNETOMETER_MAG_DIVERSE_CAL_MAG_DIVERSE_CAL_H_
+
+#include "calibration/diversity_checker/diversity_checker.h"
+#include "calibration/magnetometer/mag_cal.h"
+#include "calibration/online_calibration/common_data/calibration_callback.h"
+#include "calibration/online_calibration/common_data/calibration_data.h"
+#include "calibration/online_calibration/common_data/online_calibration.h"
+#include "calibration/online_calibration/common_data/sensor_data.h"
+
+namespace online_calibration {
+
+/*
+ * This class is a wrapper for the magnetometer offset calibration with
+ * diversity checking.
+ */
+class MagDiverseCal final : public OnlineCalibration<CalibrationDataThreeAxis> {
+ public:
+  MagDiverseCal() = default;
+
+  // Creates an MagDiverseCal with specified algorithm parameters.
+  MagDiverseCal(const MagCalParameters& mag_cal_parameters,
+                const DiversityCheckerParameters& diversity_parameters) {
+    Initialize(mag_cal_parameters, diversity_parameters);
+  }
+
+  // Initializes with specified algorithm parameters.
+  void Initialize(const MagCalParameters& mag_cal_parameters,
+                  const DiversityCheckerParameters& diversity_parameters);
+
+  // Sends new sensor data to the calibration algorithm, and returns the state
+  // of the calibration update flags, 'cal_update_polling_flags_'.
+  CalibrationTypeFlags SetMeasurement(const SensorData& sample) final;
+
+  // Sets the initial calibration data of the calibration algorithm. Returns
+  // true if set successfully.
+  bool SetInitialCalibration(
+      const CalibrationDataThreeAxis& input_cal_data) final;
+
+  // Returns the calibration sensor type.
+  SensorType get_sensor_type() const final {
+    return SensorType::kMagnetometerUt;
+  };
+
+ private:
+  // MagCal algorithm data structure.
+  MagCal mag_cal_;
+};
+
+}  // namespace online_calibration
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_ONLINE_CALIBRATION_MAGNETOMETER_MAG_DIVERSE_CAL_MAG_DIVERSE_CAL_H_
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_cal.c b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
index d92f1da..908d383 100644
--- a/firmware/os/algos/calibration/over_temp/over_temp_cal.c
+++ b/firmware/os/algos/calibration/over_temp/over_temp_cal.c
@@ -18,11 +18,9 @@
 
 #include <float.h>
 #include <math.h>
-#include <stdio.h>
 #include <string.h>
 
 #include "calibration/util/cal_log.h"
-#include "common/math/macros.h"
 #include "util/nano_assert.h"
 
 /////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
@@ -32,40 +30,39 @@
 
 // Defines the default weighting function for the linear model fit routine.
 // Weighting = 10.0; for offsets newer than 5 minutes.
-static const struct OverTempCalWeightPt kOtcDefaultWeight0 = {
+static const struct OverTempCalWeight kOtcDefaultWeight0 = {
     .offset_age_nanos = MIN_TO_NANOS(5),
     .weight = 10.0f,
 };
 
 // Weighting = 0.1; for offsets newer than 15 minutes.
-static const struct OverTempCalWeightPt kOtcDefaultWeight1 = {
+static const struct OverTempCalWeight kOtcDefaultWeight1 = {
     .offset_age_nanos = MIN_TO_NANOS(15),
     .weight = 0.1f,
 };
 
 // The default weighting used for all older offsets.
-#define OTC_MIN_WEIGHT_VALUE  (0.04f)
+#define OTC_MIN_WEIGHT_VALUE (0.04f)
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // A debug version label to help with tracking results.
-#define OTC_DEBUG_VERSION_STRING "[July 05, 2017]"
+#define OTC_DEBUG_VERSION_STRING "[Jan 10, 2018]"
 
 // The time interval used to throttle debug messaging (100msec).
 #define OTC_WAIT_TIME_NANOS (SEC_TO_NANOS(0.1))
 
-// The time interval used to throttle temperture print messaging (1 second).
+// The time interval used to throttle temperature print messaging (1 second).
 #define OTC_PRINT_TEMP_NANOS (SEC_TO_NANOS(1))
 
 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
-static const char  kDebugAxisLabel[3] = "XYZ";
+static const char kDebugAxisLabel[3] = "XYZ";
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
 /////// FORWARD DECLARATIONS //////////////////////////////////////////////////
 
 // Updates the latest received model estimate data.
 static void setLatestEstimate(struct OverTempCal *over_temp_cal,
-                              const float *offset, float offset_temp_celsius,
-                              uint64_t timestamp_nanos);
+                              const float *offset, float offset_temp_celsius);
 
 /*
  * Determines if a new over-temperature model fit should be performed, and then
@@ -103,11 +100,10 @@
  * Since it may take a while for an empty model to build up enough data to start
  * producing new model parameter updates, the model collection can be
  * jump-started by using the new model parameters to insert "fake" data in place
- * of actual sensor offset data. 'timestamp_nanos' sets the timestamp for the
- * new model data.
+ * of actual sensor offset data. The new model data 'offset_age_nanos' is set to
+ * zero.
  */
-static bool jumpStartModelData(struct OverTempCal *over_temp_cal,
-                               uint64_t timestamp_nanos);
+static bool jumpStartModelData(struct OverTempCal *over_temp_cal);
 
 /*
  * Computes a new model fit and provides updated model parameters for the
@@ -116,7 +112,6 @@
  *
  * INPUTS:
  *   over_temp_cal:    Over-temp data structure.
- *   timestamp_nanos:  Current timestamp for the model update.
  * OUTPUTS:
  *   temp_sensitivity: Updated modeled temperature sensitivity (array).
  *   sensor_intercept: Updated model intercept (array).
@@ -127,8 +122,7 @@
  * Numerical Recipes: The Art of Scientific Computing. Cambridge, 1992.
  */
 static void updateModel(const struct OverTempCal *over_temp_cal,
-                        uint64_t timestamp_nanos, float *temp_sensitivity,
-                        float *sensor_intercept);
+                        float *temp_sensitivity, float *sensor_intercept);
 
 /*
  * Computes a new over-temperature compensated offset estimate based on the
@@ -183,7 +177,8 @@
 // Returns "true" if the candidate linear model parameters are within the valid
 // range, and not all zeros.
 static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
-                   float temp_sensitivity, float sensor_intercept);
+                                  float temp_sensitivity,
+                                  float sensor_intercept);
 
 // Returns "true" if 'offset' and 'offset_temp_celsius' is valid.
 static bool isValidOtcOffset(const float *offset, float offset_temp_celsius);
@@ -191,8 +186,12 @@
 // Returns the least-squares weight based on the age of a particular offset
 // estimate.
 static float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal,
-                                       uint64_t offset_timestamp_nanos,
-                                       uint64_t current_timestamp_nanos);
+                                       uint64_t offset_age_nanos);
+
+// Computes the age increment, adds it to the age of each OTC model data point,
+// and resets the age update counter.
+static void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal,
+                                  uint64_t timestamp_nanos);
 
 // Updates 'compensated_offset' using the linear OTC model.
 static void compensateWithLinearModel(struct OverTempCal *over_temp_cal,
@@ -208,9 +207,10 @@
                                               float delta_temp_celsius);
 
 // Provides an over-temperature compensated offset based on the 'estimate'.
-static void compensateWithEstimate(
-    struct OverTempCal *over_temp_cal, uint64_t timestamp_nanos,
-    struct OverTempCalDataPt *estimate, float temperature_celsius);
+static void compensateWithEstimate(struct OverTempCal *over_temp_cal,
+                                   uint64_t timestamp_nanos,
+                                   struct OverTempModelThreeAxis *estimate,
+                                   float temperature_celsius);
 
 // Evaluates the nearest-temperature compensation (with linear extrapolation
 // term due to temperature), and compares it with the compensation due to
@@ -233,7 +233,7 @@
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // This helper function stores all of the debug tracking information necessary
 // for printing log messages.
-static void updateDebugData(struct OverTempCal* over_temp_cal);
+static void updateDebugData(struct OverTempCal *over_temp_cal);
 
 // Helper function that creates tag strings useful for identifying specific
 // debug output data (embedded system friendly; not all systems have 'sprintf').
@@ -251,12 +251,7 @@
 /////// FUNCTION DEFINITIONS //////////////////////////////////////////////////
 
 void overTempCalInit(struct OverTempCal *over_temp_cal,
-                     size_t min_num_model_pts,
-                     uint64_t min_temp_update_period_nanos,
-                     float delta_temp_per_bin, float jump_tolerance,
-                     float outlier_limit, uint64_t age_limit_nanos,
-                     float temp_sensitivity_limit, float sensor_intercept_limit,
-                     float significant_offset_change, bool over_temp_enable) {
+                     const struct OverTempCalParameters *parameters) {
   ASSERT_NOT_NULL(over_temp_cal);
 
   // Clears OverTempCal memory.
@@ -264,7 +259,7 @@
 
   // Initializes the pointers to important sensor offset estimates.
   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
-  over_temp_cal->latest_offset  = NULL;
+  over_temp_cal->latest_offset = NULL;
 
   // Initializes the OTC linear model parameters.
   resetOtcLinearModel(over_temp_cal);
@@ -272,25 +267,23 @@
   // Initializes the model identification parameters.
   over_temp_cal->new_overtemp_model_available = false;
   over_temp_cal->new_overtemp_offset_available = false;
-  over_temp_cal->min_num_model_pts = min_num_model_pts;
-  over_temp_cal->min_temp_update_period_nanos = min_temp_update_period_nanos;
-  over_temp_cal->delta_temp_per_bin = delta_temp_per_bin;
-  over_temp_cal->jump_tolerance = jump_tolerance;
-  over_temp_cal->outlier_limit = outlier_limit;
-  over_temp_cal->age_limit_nanos = age_limit_nanos;
-  over_temp_cal->temp_sensitivity_limit = temp_sensitivity_limit;
-  over_temp_cal->sensor_intercept_limit = sensor_intercept_limit;
-  over_temp_cal->significant_offset_change = significant_offset_change;
-  over_temp_cal->over_temp_enable = over_temp_enable;
+  over_temp_cal->min_num_model_pts = parameters->min_num_model_pts;
+  over_temp_cal->min_temp_update_period_nanos =
+      parameters->min_temp_update_period_nanos;
+  over_temp_cal->delta_temp_per_bin = parameters->delta_temp_per_bin;
+  over_temp_cal->jump_tolerance = parameters->jump_tolerance;
+  over_temp_cal->outlier_limit = parameters->outlier_limit;
+  over_temp_cal->age_limit_nanos = parameters->age_limit_nanos;
+  over_temp_cal->temp_sensitivity_limit = parameters->temp_sensitivity_limit;
+  over_temp_cal->sensor_intercept_limit = parameters->sensor_intercept_limit;
+  over_temp_cal->significant_offset_change =
+      parameters->significant_offset_change;
+  over_temp_cal->over_temp_enable = parameters->over_temp_enable;
 
   // Initializes the over-temperature compensated offset temperature.
   over_temp_cal->compensated_offset.offset_temp_celsius =
       OTC_TEMP_INVALID_CELSIUS;
 
-  // Defines the default weighting function for the linear model fit routine.
-  overTempSetWeightingFunction(over_temp_cal, 0, &kOtcDefaultWeight0);
-  overTempSetWeightingFunction(over_temp_cal, 1, &kOtcDefaultWeight1);
-
 #ifdef OVERTEMPCAL_DBG_ENABLED
   // Sets the default sensor descriptors for debugging.
   overTempCalDebugDescriptors(over_temp_cal, "OVER_TEMP_CAL", "mDPS",
@@ -305,6 +298,10 @@
                   "Over-temperature compensation DISABLED.");
   }
 #endif  // OVERTEMPCAL_DBG_ENABLED
+
+  // Defines the default weighting function for the linear model fit routine.
+  overTempValidateAndSetWeight(over_temp_cal, 0, &kOtcDefaultWeight0);
+  overTempValidateAndSetWeight(over_temp_cal, 1, &kOtcDefaultWeight1);
 }
 
 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
@@ -332,8 +329,7 @@
 
   // Model "Jump-Start".
   const bool model_jump_started =
-      (jump_start_model) ? jumpStartModelData(over_temp_cal, timestamp_nanos)
-                         : false;
+      jump_start_model ? jumpStartModelData(over_temp_cal) : false;
 
   if (!model_jump_started) {
     // Checks that the new offset data is valid.
@@ -342,7 +338,7 @@
       memcpy(over_temp_cal->model_data[0].offset, offset,
              sizeof(over_temp_cal->model_data[0].offset));
       over_temp_cal->model_data[0].offset_temp_celsius = offset_temp_celsius;
-      over_temp_cal->model_data[0].timestamp_nanos = timestamp_nanos;
+      over_temp_cal->model_data[0].offset_age_nanos = 0;
       over_temp_cal->num_model_pts = 1;
     } else {
       // No valid offset data to load.
@@ -361,7 +357,7 @@
     memcpy(over_temp_cal->compensated_offset.offset, offset,
            sizeof(over_temp_cal->compensated_offset.offset));
     over_temp_cal->compensated_offset.offset_temp_celsius = offset_temp_celsius;
-    over_temp_cal->compensated_offset.timestamp_nanos = timestamp_nanos;
+    over_temp_cal->compensated_offset.offset_age_nanos = 0;
   }
 
   // Resets the latest offset pointer. There are no new offset estimates to
@@ -435,7 +431,7 @@
 
 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
                              size_t data_length, uint64_t timestamp_nanos,
-                             const struct OverTempCalDataPt *model_data) {
+                             const struct OverTempModelThreeAxis *model_data) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT_NOT_NULL(model_data);
 
@@ -446,11 +442,7 @@
     if (isValidOtcOffset(model_data[i].offset,
                          model_data[i].offset_temp_celsius)) {
       memcpy(&over_temp_cal->model_data[i], &model_data[i],
-             sizeof(struct OverTempCalDataPt));
-
-      // Updates the model time stamps to the current load time.
-      over_temp_cal->model_data[i].timestamp_nanos = timestamp_nanos;
-
+             sizeof(struct OverTempModelThreeAxis));
       valid_data_count++;
     }
   }
@@ -491,11 +483,11 @@
 
 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
                              size_t *data_length,
-                             struct OverTempCalDataPt *model_data) {
+                             struct OverTempModelThreeAxis *model_data) {
   ASSERT_NOT_NULL(over_temp_cal);
   *data_length = over_temp_cal->num_model_pts;
   memcpy(model_data, over_temp_cal->model_data,
-         over_temp_cal->num_model_pts * sizeof(struct OverTempCalDataPt));
+         over_temp_cal->num_model_pts * sizeof(struct OverTempModelThreeAxis));
 }
 
 void overTempCalGetOffset(struct OverTempCal *over_temp_cal,
@@ -559,6 +551,9 @@
   ASSERT_NOT_NULL(offset);
   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
 
+  // Updates the age of each OTC model data point.
+  modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos);
+
   // Checks that the new offset data is valid, returns if bad.
   if (!isValidOtcOffset(offset, temperature_celsius)) {
     return;
@@ -586,8 +581,8 @@
       createDebugTag(over_temp_cal, ":OUTLIER]");
       CAL_DEBUG_LOG(
           over_temp_cal->otc_debug_tag,
-          "Offset|Temperature|Time [%s|C|nsec]: "
-          CAL_FORMAT_3DIGITS_TRIPLET ", " CAL_FORMAT_3DIGITS ", %llu",
+          "Offset|Temperature|Time [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
+          ", " CAL_FORMAT_3DIGITS ", %llu",
           over_temp_cal->otc_unit_tag,
           CAL_ENCODE_FLOAT(offset[0] * over_temp_cal->otc_unit_conversion, 3),
           CAL_ENCODE_FLOAT(offset[1] * over_temp_cal->otc_unit_conversion, 3),
@@ -648,8 +643,8 @@
       //    oldest data with the incoming one.
       over_temp_cal->latest_offset = &over_temp_cal->model_data[0];
       for (size_t i = 1; i < over_temp_cal->num_model_pts; i++) {
-        if (over_temp_cal->latest_offset->timestamp_nanos <
-            over_temp_cal->model_data[i].timestamp_nanos) {
+        if (over_temp_cal->latest_offset->offset_age_nanos <
+            over_temp_cal->model_data[i].offset_age_nanos) {
           over_temp_cal->latest_offset = &over_temp_cal->model_data[i];
         }
       }
@@ -657,8 +652,7 @@
   }
 
   // Updates the latest model estimate data.
-  setLatestEstimate(over_temp_cal, offset, temperature_celsius,
-                    timestamp_nanos);
+  setLatestEstimate(over_temp_cal, offset, temperature_celsius);
 
   // The latest offset estimate is the nearest temperature offset.
   over_temp_cal->nearest_offset = over_temp_cal->latest_offset;
@@ -712,6 +706,13 @@
 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
+  // Updates the age of each OTC model data point.
+  if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
+          timestamp_nanos, over_temp_cal->last_age_update_nanos,
+          OTC_MODEL_AGE_UPDATE_NANOS)) {
+    modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos);
+  }
+
   // This check throttles new OTC offset compensation updates so that high data
   // rate temperature samples do not cause excessive computational burden. Note,
   // temperature sensor updates are expected to potentially increase the data
@@ -720,7 +721,7 @@
   if (!NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
           timestamp_nanos, over_temp_cal->last_offset_update_nanos,
           over_temp_cal->min_temp_update_period_nanos)) {
-    return; // Time interval too short, skip further data processing.
+    return;  // Time interval too short, skip further data processing.
   }
 
   // Checks that the offset temperature is within a valid range, saturates if
@@ -746,8 +747,8 @@
 }
 
 void overTempGetModelError(const struct OverTempCal *over_temp_cal,
-                   const float *temp_sensitivity, const float *sensor_intercept,
-                   float *max_error) {
+                           const float *temp_sensitivity,
+                           const float *sensor_intercept, float *max_error) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT_NOT_NULL(temp_sensitivity);
   ASSERT_NOT_NULL(sensor_intercept);
@@ -770,14 +771,34 @@
   }
 }
 
-// TODO(davejacobs): Refactor to implement a compliance check on the storage of
-// 'offset_age_nanos' to ensure a monotonically increasing order with index.
-void overTempSetWeightingFunction(
+bool overTempValidateAndSetWeight(
     struct OverTempCal *over_temp_cal, size_t index,
-    const struct OverTempCalWeightPt *new_otc_weight) {
-  if (index < OTC_NUM_WEIGHT_LEVELS) {
-    over_temp_cal->weighting_function[index] = *new_otc_weight;
+    const struct OverTempCalWeight *new_otc_weight) {
+  ASSERT_NOT_NULL(over_temp_cal);
+  ASSERT_NOT_NULL(new_otc_weight);
+
+  // The input weighting coefficient must be positive.
+  if (new_otc_weight->weight <= 0.0f) {
+#ifdef OVERTEMPCAL_DBG_ENABLED
+    createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]");
+    CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Invalid weight: Must > 0.");
+#endif  // OVERTEMPCAL_DBG_ENABLED
+    return false;
   }
+
+  // Ensures that the 'index-1' weight's age is younger.
+  if (index == 0 ||
+      over_temp_cal->weighting_function[index - 1].offset_age_nanos <
+          new_otc_weight->offset_age_nanos) {
+    over_temp_cal->weighting_function[index] = *new_otc_weight;
+    return true;
+  }
+
+#ifdef OVERTEMPCAL_DBG_ENABLED
+  createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]");
+  CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Non monotonic weight age.");
+#endif  // OVERTEMPCAL_DBG_ENABLED
+  return false;
 }
 
 /////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
@@ -827,9 +848,10 @@
   }
 }
 
-void compensateWithEstimate(
-    struct OverTempCal *over_temp_cal, uint64_t timestamp_nanos,
-    struct OverTempCalDataPt *estimate, float temperature_celsius) {
+void compensateWithEstimate(struct OverTempCal *over_temp_cal,
+                            uint64_t timestamp_nanos,
+                            struct OverTempModelThreeAxis *estimate,
+                            float temperature_celsius) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT_NOT_NULL(estimate);
 
@@ -927,9 +949,9 @@
   // than one estimate in the model (i.e., don't want to remove all data, even
   // if it is very old [something is likely better than nothing]).
   if ((timestamp_nanos >=
-       OTC_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer) &&
+       OTC_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer_nanos) &&
       over_temp_cal->num_model_pts > 1) {
-    over_temp_cal->stale_data_timer = timestamp_nanos;  // Resets timer.
+    over_temp_cal->stale_data_timer_nanos = timestamp_nanos;  // Resets timer.
     removeStaleModelData(over_temp_cal, timestamp_nanos);
   }
 
@@ -944,12 +966,25 @@
   // not empty.
   const bool model_points_available = (over_temp_cal->num_model_pts > 0);
 
+  // To properly evaluate the logic paths that use the latest and nearest offset
+  // data below, the current age of the nearest and latest offset estimates are
+  // computed.
+  const uint64_t latest_offset_age_nanos =
+      (over_temp_cal->latest_offset != NULL)
+          ? over_temp_cal->latest_offset->offset_age_nanos + timestamp_nanos -
+                over_temp_cal->last_age_update_nanos
+          : 0;
+  const uint64_t nearest_offset_age_nanos =
+      (over_temp_cal->nearest_offset != NULL)
+          ? over_temp_cal->nearest_offset->offset_age_nanos + timestamp_nanos -
+                over_temp_cal->last_age_update_nanos
+          : 0;
+
   // True when the latest offset estimate will be used to compute a sensor
   // offset calibration estimate.
   const bool use_latest_offset_compensation =
-      over_temp_cal->latest_offset && model_points_available &&
-      timestamp_nanos < over_temp_cal->latest_offset->timestamp_nanos +
-                            OTC_USE_RECENT_OFFSET_TIME_NANOS;
+      over_temp_cal->latest_offset != NULL && model_points_available &&
+      latest_offset_age_nanos <= OTC_USE_RECENT_OFFSET_TIME_NANOS;
 
   // True when the conditions are met to use the nearest-temperature offset to
   // compute a sensor offset calibration estimate.
@@ -958,7 +993,7 @@
   //    ii. Offset temperature must be within a small neighborhood of the
   //        current measured temperature (+/- 'delta_temp_per_bin').
   const bool can_compensate_with_nearest =
-      model_points_available && over_temp_cal->nearest_offset &&
+      model_points_available && over_temp_cal->nearest_offset != NULL &&
       NANO_ABS(temperature_celsius -
                over_temp_cal->nearest_offset->offset_temp_celsius) <
           over_temp_cal->delta_temp_per_bin;
@@ -966,18 +1001,14 @@
   // True if the last received sensor offset estimate is old or non-existent.
   const bool latest_model_point_not_relevant =
       (over_temp_cal->latest_offset == NULL) ||
-      (over_temp_cal->latest_offset &&
-       NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
-           timestamp_nanos, over_temp_cal->latest_offset->timestamp_nanos,
-           OTC_OFFSET_IS_STALE_NANOS));
+      (over_temp_cal->latest_offset != NULL &&
+       latest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS);
 
   // True if the nearest-temperature offset estimate is old or non-existent.
   const bool nearest_model_point_not_relevant =
       (over_temp_cal->nearest_offset == NULL) ||
-      (over_temp_cal->nearest_offset &&
-       NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
-           timestamp_nanos, over_temp_cal->nearest_offset->timestamp_nanos,
-           OTC_OFFSET_IS_STALE_NANOS));
+      (over_temp_cal->nearest_offset != NULL &&
+       nearest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS);
 
   // ---------------------------------------------------------------------------
   // The following conditional expressions govern new OTC offset updates.
@@ -1051,26 +1082,25 @@
   over_temp_cal->new_overtemp_offset_available |= new_overtemp_offset_available;
 
   // If the offset has changed significantly, then the offset compensation
-  // vector and timestamp are updated.
+  // vector is updated.
   if (new_overtemp_offset_available) {
     memcpy(over_temp_cal->compensated_offset.offset, compensated_offset,
            sizeof(over_temp_cal->compensated_offset.offset));
-    over_temp_cal->compensated_offset.timestamp_nanos = timestamp_nanos;
     over_temp_cal->compensated_offset.offset_temp_celsius = temperature_celsius;
   }
 }
 
 void setLatestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
-                       float offset_temp_celsius, uint64_t timestamp_nanos) {
+                       float offset_temp_celsius) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT_NOT_NULL(offset);
 
-  if (over_temp_cal->latest_offset) {
+  if (over_temp_cal->latest_offset != NULL) {
     // Sets the latest over-temp calibration estimate.
     memcpy(over_temp_cal->latest_offset->offset, offset,
            sizeof(over_temp_cal->latest_offset->offset));
     over_temp_cal->latest_offset->offset_temp_celsius = offset_temp_celsius;
-    over_temp_cal->latest_offset->timestamp_nanos = timestamp_nanos;
+    over_temp_cal->latest_offset->offset_age_nanos = 0;
   }
 }
 
@@ -1098,19 +1128,17 @@
 
   // Ensures that the minimum number of points required for a model fit has been
   // satisfied.
-  if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts)
-      return;
+  if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts) return;
 
   // Updates the linear model fit.
   float temp_sensitivity[3];
   float sensor_intercept[3];
-  updateModel(over_temp_cal, timestamp_nanos, temp_sensitivity,
-              sensor_intercept);
+  updateModel(over_temp_cal, temp_sensitivity, sensor_intercept);
 
   //    2) A new set of model parameters are accepted if:
   //         i. The model fit parameters must be within certain absolute bounds:
-  //              a. NANO_ABS(temp_sensitivity) < temp_sensitivity_limit
-  //              b. NANO_ABS(sensor_intercept) < sensor_intercept_limit
+  //              a. |temp_sensitivity| < temp_sensitivity_limit
+  //              b. |sensor_intercept| < sensor_intercept_limit
   // NOTE: Model parameter updates are not qualified against model fit error
   // here to protect against the case where there is large change in the
   // temperature characteristic either during runtime (e.g., temperature
@@ -1186,9 +1214,8 @@
 
   bool removed_one = false;
   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
-    if (timestamp_nanos > over_temp_cal->model_data[i].timestamp_nanos &&
-        timestamp_nanos > over_temp_cal->age_limit_nanos +
-                              over_temp_cal->model_data[i].timestamp_nanos) {
+    if (over_temp_cal->model_data[i].offset_age_nanos >=
+        over_temp_cal->age_limit_nanos) {
       // If the latest offset was removed, then indicate this by setting it to
       // NULL.
       if (over_temp_cal->latest_offset == &over_temp_cal->model_data[i]) {
@@ -1223,7 +1250,7 @@
   createDebugTag(over_temp_cal, ":REMOVE]");
   CAL_DEBUG_LOG(
       over_temp_cal->otc_debug_tag,
-      "Offset|Temp|Time [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
+      "Offset|Temp|Age [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
       ", " CAL_FORMAT_3DIGITS ", %llu",
       over_temp_cal->otc_unit_tag,
       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[0] *
@@ -1238,21 +1265,20 @@
       CAL_ENCODE_FLOAT(
           over_temp_cal->model_data[model_index].offset_temp_celsius, 3),
       (unsigned long long int)over_temp_cal->model_data[model_index]
-          .timestamp_nanos);
+          .offset_age_nanos);
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
   // Remove the model data at 'model_index'.
   for (size_t i = model_index; i < over_temp_cal->num_model_pts - 1; i++) {
     memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1],
-           sizeof(struct OverTempCalDataPt));
+           sizeof(struct OverTempModelThreeAxis));
   }
   over_temp_cal->num_model_pts--;
 
   return true;
 }
 
-bool jumpStartModelData(struct OverTempCal *over_temp_cal,
-                        uint64_t timestamp_nanos) {
+bool jumpStartModelData(struct OverTempCal *over_temp_cal) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
 
@@ -1291,7 +1317,7 @@
           over_temp_cal->sensor_intercept[j];
     }
     over_temp_cal->model_data[i].offset_temp_celsius = offset_temp_celsius;
-    over_temp_cal->model_data[i].timestamp_nanos = timestamp_nanos;
+    over_temp_cal->model_data[i].offset_age_nanos = 0;
 
     offset_temp_celsius += over_temp_cal->delta_temp_per_bin;
     over_temp_cal->num_model_pts++;
@@ -1310,8 +1336,7 @@
 }
 
 void updateModel(const struct OverTempCal *over_temp_cal,
-                 uint64_t timestamp_nanos, float *temp_sensitivity,
-                 float *sensor_intercept) {
+                 float *temp_sensitivity, float *sensor_intercept) {
   ASSERT_NOT_NULL(over_temp_cal);
   ASSERT_NOT_NULL(temp_sensitivity);
   ASSERT_NOT_NULL(sensor_intercept);
@@ -1328,8 +1353,7 @@
   const size_t n = over_temp_cal->num_model_pts;
   for (size_t i = 0; i < n; ++i) {
     weight = evaluateWeightingFunction(
-        over_temp_cal, over_temp_cal->model_data[i].timestamp_nanos,
-        timestamp_nanos);
+        over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos);
 
     sw += weight;
     st += over_temp_cal->model_data[i].offset_temp_celsius * weight;
@@ -1343,13 +1367,11 @@
   const float inv_sw = 1.0f / sw;
   for (size_t i = 0; i < n; ++i) {
     weight = evaluateWeightingFunction(
-        over_temp_cal, over_temp_cal->model_data[i].timestamp_nanos,
-        timestamp_nanos);
+        over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos);
 
     const float t =
-        over_temp_cal->model_data[i].offset_temp_celsius -
-        st * inv_sw;
-    stt +=  weight * t * t;
+        over_temp_cal->model_data[i].offset_temp_celsius - st * inv_sw;
+    stt += weight * t * t;
     stsx += t * over_temp_cal->model_data[i].offset[0] * weight;
     stsy += t * over_temp_cal->model_data[i].offset[1] * weight;
     stsz += t * over_temp_cal->model_data[i].offset[2] * weight;
@@ -1440,13 +1462,11 @@
 }
 
 float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal,
-                                uint64_t offset_timestamp_nanos,
-                                uint64_t current_timestamp_nanos) {
+                                uint64_t offset_age_nanos) {
   ASSERT_NOT_NULL(over_temp_cal);
   for (size_t i = 0; i < OTC_NUM_WEIGHT_LEVELS; i++) {
-    if (current_timestamp_nanos <=
-        offset_timestamp_nanos +
-            over_temp_cal->weighting_function[i].offset_age_nanos) {
+    if (offset_age_nanos <=
+        over_temp_cal->weighting_function[i].offset_age_nanos) {
       return over_temp_cal->weighting_function[i].weight;
     }
   }
@@ -1455,6 +1475,21 @@
   return OTC_MIN_WEIGHT_VALUE;
 }
 
+void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal,
+                           uint64_t timestamp_nanos) {
+  ASSERT_NOT_NULL(over_temp_cal);
+  uint64_t age_increment_nanos =
+      timestamp_nanos - over_temp_cal->last_age_update_nanos;
+
+  // Resets the age update counter.
+  over_temp_cal->last_age_update_nanos = timestamp_nanos;
+
+  // Updates the model dataset ages.
+  for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
+    over_temp_cal->model_data[i].offset_age_nanos += age_increment_nanos;
+  }
+}
+
 /////// DEBUG FUNCTION DEFINITIONS ////////////////////////////////////////////
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
@@ -1468,7 +1503,7 @@
       new_debug_tag, strlen(new_debug_tag) + 1);
 }
 
-void updateDebugData(struct OverTempCal* over_temp_cal) {
+void updateDebugData(struct OverTempCal *over_temp_cal) {
   ASSERT_NOT_NULL(over_temp_cal);
 
   // Only update this data if debug printing is not currently in progress
@@ -1502,13 +1537,13 @@
 
   // If 'latest_offset' is defined the copy the data for debug printing.
   // Otherwise, the current compensated offset will be printed.
-  if (over_temp_cal->latest_offset) {
+  if (over_temp_cal->latest_offset != NULL) {
     memcpy(&over_temp_cal->debug_overtempcal.latest_offset,
-           over_temp_cal->latest_offset, sizeof(struct OverTempCalDataPt));
+           over_temp_cal->latest_offset, sizeof(struct OverTempModelThreeAxis));
   } else {
     memcpy(&over_temp_cal->debug_overtempcal.latest_offset,
            &over_temp_cal->compensated_offset,
-           sizeof(struct OverTempCalDataPt));
+           sizeof(struct OverTempModelThreeAxis));
   }
 
   // Total number of OTC model data points.
@@ -1516,9 +1551,9 @@
 
   // Computes the maximum error over all of the model data.
   overTempGetModelError(over_temp_cal,
-                over_temp_cal->debug_overtempcal.temp_sensitivity,
-                over_temp_cal->debug_overtempcal.sensor_intercept,
-                over_temp_cal->debug_overtempcal.max_error);
+                        over_temp_cal->debug_overtempcal.temp_sensitivity,
+                        over_temp_cal->debug_overtempcal.sensor_intercept,
+                        over_temp_cal->debug_overtempcal.max_error);
 }
 
 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
@@ -1554,7 +1589,7 @@
       // Prints out the latest offset estimate (input data).
       CAL_DEBUG_LOG(
           over_temp_cal->otc_debug_tag,
-          "Cal#|Offset|Temp|Time [%s|C|nsec]: %lu, " CAL_FORMAT_3DIGITS_TRIPLET
+          "Cal#|Offset|Temp|Age [%s|C|nsec]: %lu, " CAL_FORMAT_3DIGITS_TRIPLET
           ", " CAL_FORMAT_3DIGITS ", %llu",
           over_temp_cal->otc_unit_tag,
           (unsigned long int)over_temp_cal->debug_num_estimates,
@@ -1574,34 +1609,35 @@
                                .offset_temp_celsius,
                            3),
           (unsigned long long int)
-              over_temp_cal->debug_overtempcal.latest_offset.timestamp_nanos);
+              over_temp_cal->debug_overtempcal.latest_offset.offset_age_nanos);
 
+      // clang-format off
       over_temp_cal->wait_timer_nanos =
           timestamp_nanos;                          // Starts the wait timer.
       over_temp_cal->next_state =
           OTC_PRINT_MODEL_PARAMETERS;               // Sets the next state.
       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
+      // clang-format on
       break;
 
     case OTC_PRINT_MODEL_PARAMETERS:
       // Prints out the model parameters.
-      CAL_DEBUG_LOG(
-          over_temp_cal->otc_debug_tag,
-          "Cal#|Sensitivity [%s/C]: %lu, " CAL_FORMAT_3DIGITS_TRIPLET,
-          over_temp_cal->otc_unit_tag,
-          (unsigned long int)over_temp_cal->debug_num_estimates,
-          CAL_ENCODE_FLOAT(
-              over_temp_cal->debug_overtempcal.temp_sensitivity[0] *
-                  over_temp_cal->otc_unit_conversion,
-              3),
-          CAL_ENCODE_FLOAT(
-              over_temp_cal->debug_overtempcal.temp_sensitivity[1] *
-                  over_temp_cal->otc_unit_conversion,
-              3),
-          CAL_ENCODE_FLOAT(
-              over_temp_cal->debug_overtempcal.temp_sensitivity[2] *
-                  over_temp_cal->otc_unit_conversion,
-              3));
+      CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
+                    "Cal#|Sensitivity [%s/C]: %lu, " CAL_FORMAT_3DIGITS_TRIPLET,
+                    over_temp_cal->otc_unit_tag,
+                    (unsigned long int)over_temp_cal->debug_num_estimates,
+                    CAL_ENCODE_FLOAT(
+                        over_temp_cal->debug_overtempcal.temp_sensitivity[0] *
+                            over_temp_cal->otc_unit_conversion,
+                        3),
+                    CAL_ENCODE_FLOAT(
+                        over_temp_cal->debug_overtempcal.temp_sensitivity[1] *
+                            over_temp_cal->otc_unit_conversion,
+                        3),
+                    CAL_ENCODE_FLOAT(
+                        over_temp_cal->debug_overtempcal.temp_sensitivity[2] *
+                            over_temp_cal->otc_unit_conversion,
+                        3));
 
       CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
                     "Cal#|Intercept [%s]: %lu, " CAL_FORMAT_3DIGITS_TRIPLET,
@@ -1621,8 +1657,9 @@
                         3));
 
       over_temp_cal->wait_timer_nanos =
-          timestamp_nanos;                          // Starts the wait timer.
-      over_temp_cal->next_state = OTC_PRINT_MODEL_ERROR;  // Sets the next state.
+          timestamp_nanos;  // Starts the wait timer.
+      over_temp_cal->next_state =
+          OTC_PRINT_MODEL_ERROR;                    // Sets the next state.
       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
       break;
 
@@ -1648,7 +1685,7 @@
 
       over_temp_cal->model_counter = 0;  // Resets the model data print counter.
       over_temp_cal->wait_timer_nanos =
-          timestamp_nanos;               // Starts the wait timer.
+          timestamp_nanos;  // Starts the wait timer.
       over_temp_cal->next_state = OTC_PRINT_MODEL_DATA;  // Sets the next state.
       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
       break;
@@ -1683,22 +1720,22 @@
                 3),
             (unsigned long long int)over_temp_cal
                 ->model_data[over_temp_cal->model_counter]
-                .timestamp_nanos);
+                .offset_age_nanos);
 
         over_temp_cal->model_counter++;
         over_temp_cal->wait_timer_nanos =
-            timestamp_nanos;                        // Starts the wait timer.
+            timestamp_nanos;  // Starts the wait timer.
         over_temp_cal->next_state =
-            OTC_PRINT_MODEL_DATA;                   // Sets the next state.
+            OTC_PRINT_MODEL_DATA;  // Sets the next state.
         over_temp_cal->debug_state =
-            OTC_WAIT_STATE;                         // First, go to wait state.
+            OTC_WAIT_STATE;  // First, go to wait state.
       } else {
         // Sends this state machine to its idle state.
         over_temp_cal->wait_timer_nanos =
-            timestamp_nanos;                        // Starts the wait timer.
-        over_temp_cal->next_state = OTC_IDLE;       // Sets the next state.
+            timestamp_nanos;                   // Starts the wait timer.
+        over_temp_cal->next_state = OTC_IDLE;  // Sets the next state.
         over_temp_cal->debug_state =
-            OTC_WAIT_STATE;                         // First, go to wait state.
+            OTC_WAIT_STATE;  // First, go to wait state.
       }
       break;
 
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_cal.h b/firmware/os/algos/calibration/over_temp/over_temp_cal.h
index 8a404d3..4cfbfc1 100644
--- a/firmware/os/algos/calibration/over_temp/over_temp_cal.h
+++ b/firmware/os/algos/calibration/over_temp/over_temp_cal.h
@@ -100,6 +100,9 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "calibration/over_temp/over_temp_model.h"
+#include "common/math/macros.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -129,6 +132,9 @@
 // function.
 #define OTC_NUM_WEIGHT_LEVELS (2)
 
+// The time interval used to update the model data age.
+#define OTC_MODEL_AGE_UPDATE_NANOS (MIN_TO_NANOS(1))
+
 // Rate-limits the check of old data to every 2 hours.
 #define OTC_STALE_CHECK_TIME_NANOS (HRS_TO_NANOS(2))
 
@@ -143,7 +149,7 @@
 #define OTC_REFRESH_MODEL_NANOS (SEC_TO_NANOS(30))
 
 // Defines a weighting function value for the linear model fit routine.
-struct OverTempCalWeightPt {
+struct OverTempCalWeight {
   // The age limit below which an offset will use this weight value.
   uint64_t offset_age_nanos;
 
@@ -151,14 +157,6 @@
   float weight;
 };
 
-// Over-temperature sensor offset estimate structure.
-struct OverTempCalDataPt {
-  // Sensor offset estimate, temperature, and timestamp.
-  uint64_t timestamp_nanos;   // [nanoseconds]
-  float offset_temp_celsius;  // [Celsius]
-  float offset[3];
-};
-
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // Debug printout state enumeration.
 enum OverTempCalDebugState {
@@ -173,7 +171,7 @@
 // OverTempCal debug information/data tracking structure.
 struct DebugOverTempCal {
   // The latest received offset estimate data.
-  struct OverTempCalDataPt latest_offset;
+  struct OverTempModelThreeAxis latest_offset;
 
   // The maximum model error over all model_data points.
   float max_error[3];
@@ -184,38 +182,55 @@
 };
 #endif  // OVERTEMPCAL_DBG_ENABLED
 
+// OverTempCal algorithm parameters (see OverTempCal for details).
+struct OverTempCalParameters {
+  uint64_t min_temp_update_period_nanos;
+  uint64_t age_limit_nanos;
+  float delta_temp_per_bin;         // [Celsius/bin]
+  float jump_tolerance;             // [sensor units]
+  float outlier_limit;              // [sensor units/Celsius]
+  float temp_sensitivity_limit;     // [sensor units/Celsius]
+  float sensor_intercept_limit;     // [sensor units]
+  float significant_offset_change;  // [sensor units]
+  size_t min_num_model_pts;
+  bool over_temp_enable;
+};
+
 // The following data structure contains all of the necessary components for
 // modeling a sensor's temperature dependency and providing over-temperature
 // offset corrections.
 struct OverTempCal {
   // Storage for over-temperature model data.
-  struct OverTempCalDataPt model_data[OTC_MODEL_SIZE];
+  struct OverTempModelThreeAxis model_data[OTC_MODEL_SIZE];
 
   // Implements a weighting function to emphasize fitting a linear model to
   // younger offset estimates.
-  struct OverTempCalWeightPt weighting_function[OTC_NUM_WEIGHT_LEVELS];
+  struct OverTempCalWeight weighting_function[OTC_NUM_WEIGHT_LEVELS];
 
   // The active over-temperature compensated offset estimate data. Contains the
   // current sensor temperature at which offset compensation is performed.
-  struct OverTempCalDataPt compensated_offset;
+  struct OverTempModelThreeAxis compensated_offset;
 
   // Timer used to limit the rate at which old estimates are removed from
   // the 'model_data' collection.
-  uint64_t stale_data_timer;             // [nanoseconds]
+  uint64_t stale_data_timer_nanos;
 
   // Duration beyond which data will be removed to avoid corrupting the model
   // with drift-compromised data.
-  uint64_t age_limit_nanos;              // [nanoseconds]
+  uint64_t age_limit_nanos;
 
   // Timestamp of the last OTC offset compensation update.
-  uint64_t last_offset_update_nanos;     // [nanoseconds]
+  uint64_t last_offset_update_nanos;
 
   // Timestamp of the last OTC model update.
-  uint64_t last_model_update_nanos;      // [nanoseconds]
+  uint64_t last_model_update_nanos;
+
+  // Timestamp of the last OTC model dataset age update.
+  uint64_t last_age_update_nanos;
 
   // Limit on the minimum interval for offset update calculations resulting from
   // an arbitrarily high temperature sampling rate.
-  uint64_t min_temp_update_period_nanos;    // [nanoseconds]
+  uint64_t min_temp_update_period_nanos;
 
   ///// Online Model Identification Parameters ////////////////////////////////
   //
@@ -229,17 +244,17 @@
   //          model_temp_span >= 'num_model_pts' * delta_temp_per_bin
   //    2) A new set of model parameters are accepted if:
   //         i. The model fit parameters must be within certain absolute bounds:
-  //              a. ABS(temp_sensitivity) < temp_sensitivity_limit
-  //              b. ABS(sensor_intercept) < sensor_intercept_limit
-  float temp_sensitivity_limit;        // [sensor units/Celsius]
-  float sensor_intercept_limit;        // [sensor units]
+  //              a. |temp_sensitivity| < temp_sensitivity_limit
+  //              b. |sensor_intercept| < sensor_intercept_limit
+  float temp_sensitivity_limit;  // [sensor units/Celsius]
+  float sensor_intercept_limit;  // [sensor units]
   size_t min_num_model_pts;
 
   // Pointer to the offset estimate closest to the current sensor temperature.
-  struct OverTempCalDataPt *nearest_offset;
+  struct OverTempModelThreeAxis *nearest_offset;
 
   // Pointer to the most recent offset estimate.
-  struct OverTempCalDataPt *latest_offset;
+  struct OverTempModelThreeAxis *latest_offset;
 
   // Modeled temperature sensitivity, dOffset/dTemp [sensor_units/Celsius].
   float temp_sensitivity[3];
@@ -251,15 +266,15 @@
   // above which the model fit is preferred for providing offset compensation
   // (also applies to checks between the nearest-temperature and the current
   // compensated estimate).
-  float jump_tolerance;                // [sensor units]
+  float jump_tolerance;  // [sensor units]
 
   // A limit used to reject new offset estimates that deviate from the current
   // model fit.
-  float outlier_limit;                 // [sensor units]
+  float outlier_limit;  // [sensor units]
 
   // This parameter is used to detect offset changes that require updates to
   // system calibration and persistent memory storage.
-  float significant_offset_change;     // [sensor units]
+  float significant_offset_change;  // [sensor units]
 
   // Used to track the previous significant change in temperature.
   float last_temp_check_celsius;
@@ -282,7 +297,7 @@
   // recent estimates in cases where they arrive frequently near a given
   // temperature, and prevents model oversampling (i.e., dominance of estimates
   // concentrated at a given set of temperatures).
-  float delta_temp_per_bin;            // [Celsius/bin]
+  float delta_temp_per_bin;  // [Celsius/bin]
 
   // Total number of model data points collected.
   size_t num_model_pts;
@@ -299,8 +314,7 @@
   // overTempCalNewModelUpdateAvailable() is called. This variable indicates
   // that the following should be stored in persistent system memory:
   //    1) 'temp_sensitivity' and 'sensor_intercept'.
-  //    2) The 'compensated_offset' offset data
-  //       (saving timestamp information is not required).
+  //    2) The 'compensated_offset' offset data.
   bool new_overtemp_model_available;
 
   // True when a new offset estimate has been computed and registers as a
@@ -320,15 +334,15 @@
   uint64_t temperature_print_timer;
 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
 
-  size_t model_counter;                // Model output print counter.
-  float otc_unit_conversion;           // Unit conversion for debug display.
-  char otc_unit_tag[16];               // Unit descriptor (e.g., "mDPS").
-  char otc_sensor_tag[16];             // OTC sensor descriptor (e.g., "GYRO").
-  char otc_debug_tag[32];              // Temporary string descriptor.
-  size_t debug_num_model_updates;      // Total number of model updates.
-  size_t debug_num_estimates;          // Total number of offset estimates.
-  bool debug_print_trigger;            // Flag used to trigger data printout.
-#endif  // OVERTEMPCAL_DBG_ENABLED
+  size_t model_counter;            // Model output print counter.
+  float otc_unit_conversion;       // Unit conversion for debug display.
+  char otc_unit_tag[16];           // Unit descriptor (e.g., "mDPS").
+  char otc_sensor_tag[16];         // OTC sensor descriptor (e.g., "GYRO").
+  char otc_debug_tag[32];          // Temporary string descriptor.
+  size_t debug_num_model_updates;  // Total number of model updates.
+  size_t debug_num_estimates;      // Total number of offset estimates.
+  bool debug_print_trigger;        // Flag used to trigger data printout.
+#endif                             // OVERTEMPCAL_DBG_ENABLED
 };
 
 /////// FUNCTION PROTOTYPES ///////////////////////////////////////////////////
@@ -338,6 +352,9 @@
  *
  * INPUTS:
  *   over_temp_cal:             Over-temp main data structure.
+ *   parameters:                An algorithm parameters that contains the
+ *                              following initialization variables.
+ * [parameters]:
  *   min_num_model_pts:         Minimum number of model points per model
  *                              calculation update.
  *   min_temp_update_period_nanos: Limits the rate of offset updates due to an
@@ -351,19 +368,14 @@
  *   temp_sensitivity_limit:    Values that define the upper limits for the
  *   sensor_intercept_limit:    model parameters. The acceptance of new model
  *                              parameters must satisfy:
- *                          i.  ABS(temp_sensitivity) < temp_sensitivity_limit
- *                          ii. ABS(sensor_intercept) < sensor_intercept_limit
+ *                          i.  |temp_sensitivity| < temp_sensitivity_limit
+ *                          ii. |sensor_intercept| < sensor_intercept_limit
  *   significant_offset_change  Minimum limit that triggers offset updates.
  *   over_temp_enable:          Flag that determines whether over-temp sensor
  *                              offset compensation is applied.
  */
 void overTempCalInit(struct OverTempCal *over_temp_cal,
-                     size_t min_num_model_pts,
-                     uint64_t min_temp_update_period_nanos,
-                     float delta_temp_per_bin, float jump_tolerance,
-                     float outlier_limit, uint64_t age_limit_nanos,
-                     float temp_sensitivity_limit, float sensor_intercept_limit,
-                     float significant_offset_change, bool over_temp_enable);
+                     const struct OverTempCalParameters *parameters);
 
 /*
  * Sets the over-temp calibration model parameters.
@@ -417,7 +429,7 @@
  */
 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
                              size_t data_length, uint64_t timestamp_nanos,
-                             const struct OverTempCalDataPt *model_data);
+                             const struct OverTempModelThreeAxis *model_data);
 
 /*
  * Gets the over-temp compensation model data set.
@@ -432,7 +444,7 @@
  */
 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
                              size_t *data_length,
-                             struct OverTempCalDataPt *model_data);
+                             struct OverTempModelThreeAxis *model_data);
 
 /*
  * Gets the current over-temp compensated offset estimate data.
@@ -472,8 +484,8 @@
 bool overTempCalNewOffsetAvailable(struct OverTempCal *over_temp_cal);
 
 /*
- * Updates the sensor's offset estimate and conditionally assimilates it into
- * the over-temp model data set, 'model_data'.
+ * Updates the sensor's offset estimate and conditionally incorporates it into
+ * the over-temp model data set, 'model_data'. Updates the model dataset age.
  *
  * INPUTS:
  *   over_temp_cal:       Over-temp data structure.
@@ -491,7 +503,9 @@
 // Updates the temperature at which the offset compensation is performed (i.e.,
 // the current measured temperature value). This function is provided mainly for
 // flexibility since temperature updates may come in from a source other than
-// the sensor itself, and at a different rate.
+// the sensor itself, and at a different rate. Since this function is
+// periodically called, it is also used to update the age of the model
+// estimates.
 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
                                uint64_t timestamp_nanos,
                                float temperature_celsius);
@@ -527,7 +541,9 @@
  * age is less than 'offset_age_nanos'. NOTE: The ordering of the
  * 'offset_age_nanos' values in the weight function array should be
  * monotonically increasing from lowest index to highest so that weighting
- * selection can be conveniently evaluated.
+ * selection can be conveniently evaluated. A simple compliance check is
+ * applied, and 'true' is returned when the input weight is positive and older
+ * than the 'index-1' weight's age.
  *
  * INPUTS:
  *   over_temp_cal:    Over-temp data structure.
@@ -536,9 +552,9 @@
  *                     value and corresponding age limit below which an offset
  *                     will use the weight.
  */
-void overTempSetWeightingFunction(
+bool overTempValidateAndSetWeight(
     struct OverTempCal *over_temp_cal, size_t index,
-    const struct OverTempCalWeightPt *new_otc_weight);
+    const struct OverTempCalWeight *new_otc_weight);
 
 #ifdef OVERTEMPCAL_DBG_ENABLED
 // This debug printout function assumes the input sensor data is a gyroscope
diff --git a/firmware/os/algos/calibration/over_temp/over_temp_model.h b/firmware/os/algos/calibration/over_temp/over_temp_model.h
new file mode 100644
index 0000000..18e2bef
--- /dev/null
+++ b/firmware/os/algos/calibration/over_temp/over_temp_model.h
@@ -0,0 +1,38 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_MODEL_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_MODEL_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Over-temperature data structures that contain a modeled sensor offset
+ * estimate, an associated temperature, and the age of the data point since it
+ * first entered the model.
+ */
+
+struct OverTempModelThreeAxis {
+  // Sensor offset estimate, temperature, and age timestamp.
+  uint64_t offset_age_nanos;  // [nanoseconds]
+  float offset_temp_celsius;  // [Celsius]
+
+  // Three-axis offset estimate (indices: 0=x, 1=y, 2=z).
+  float offset[3];
+};
+
+struct OverTempModelSingleAxis {
+  // Sensor offset estimate, temperature, and age timestamp.
+  uint64_t offset_age_nanos;  // [nanoseconds]
+  float offset_temp_celsius;  // [Celsius]
+
+  // Single-axis offset estimate.
+  float offset;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_MODEL_H_
diff --git a/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.c b/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.c
new file mode 100644
index 0000000..a501331
--- /dev/null
+++ b/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "calibration/sample_rate_estimator/sample_rate_estimator.h"
+
+#include <string.h>
+
+#include "common/math/macros.h"
+#include "util/nano_assert.h"
+
+// Helper function used to reset the sampling rate estimator accumulator.
+static void sampleRateEstimatorResetAccumulator(
+    struct SampleRateEstimator* sample_rate_estimator) {
+  sample_rate_estimator->last_timestamp_nanos = 0.0f;
+  sample_rate_estimator->interval_accumulator_nanos = 0.0f;
+  sample_rate_estimator->num_intervals_collected = 0;
+}
+
+void sampleRateEstimatorInit(struct SampleRateEstimator* sample_rate_estimator,
+                             size_t num_intervals_to_collect,
+                             float max_interval_sec) {
+  ASSERT_NOT_NULL(sample_rate_estimator);
+  memset(sample_rate_estimator, 0, sizeof(struct SampleRateEstimator));
+  sample_rate_estimator->mean_sampling_rate_estimate_hz =
+      SAMPLE_RATE_ESTIMATOR_INVALID_SAMPLE_RATE_HZ;
+  sample_rate_estimator->num_intervals_to_collect = num_intervals_to_collect;
+  sample_rate_estimator->max_interval_nanos =
+      max_interval_sec * SEC_TO_NANOS(1);
+}
+
+float sampleRateEstimatorGetHz(
+    struct SampleRateEstimator* sample_rate_estimator) {
+  sample_rate_estimator->new_sampling_rate_estimate_ready = false;
+  return sample_rate_estimator->mean_sampling_rate_estimate_hz;
+}
+
+void sampleRateEstimatorUpdate(
+    struct SampleRateEstimator* sample_rate_estimator,
+    uint64_t timestamp_nanos) {
+  // Resets the current interval capture and returns if:
+  //   1. A bad timestamp was received (i.e., time not monotonic).
+  //   2. 'last_timestamp_nanos' is zero. NOTE: 'last_timestamp_nanos' = 0
+  //      indicates that the first complete time interval has not been captured
+  //      yet (so, set it and return).
+  if (timestamp_nanos <= sample_rate_estimator->last_timestamp_nanos ||
+      sample_rate_estimator->last_timestamp_nanos == 0) {
+    sample_rate_estimator->last_timestamp_nanos = timestamp_nanos;
+    return;
+  }
+
+  // Computes the current sampling interval. This conversion will be very fast
+  // for intervals less than ~4.3 seconds (i.e., 2^32 nano-seconds).
+  const float next_interval_nanos = floatFromUint64(
+      timestamp_nanos - sample_rate_estimator->last_timestamp_nanos);
+
+  // Helps prevent corruption of the estimator when there are gaps in the input
+  // sampling intervals greater than 'max_interval_nanos' (i.e., intermittant
+  // periods where there are no input timestamps).
+  if (next_interval_nanos >= sample_rate_estimator->max_interval_nanos) {
+    // Resets the estimator and returns.
+    sampleRateEstimatorResetAccumulator(sample_rate_estimator);
+    return;
+  }
+
+  // Accumulates the next sampling interval.
+  sample_rate_estimator->interval_accumulator_nanos += next_interval_nanos;
+  sample_rate_estimator->last_timestamp_nanos = timestamp_nanos;
+  sample_rate_estimator->num_intervals_collected++;
+
+  // If the number of collected time intervals exceed the target number, then
+  // this computes a new sample rate estimate.
+  if (sample_rate_estimator->num_intervals_collected >
+      sample_rate_estimator->num_intervals_to_collect) {
+    sample_rate_estimator->mean_sampling_rate_estimate_hz =
+        SEC_TO_NANOS(1) * sample_rate_estimator->num_intervals_collected /
+        sample_rate_estimator->interval_accumulator_nanos;
+
+    // Sets the polling flag to indicate that a new estimate is ready.
+    sample_rate_estimator->new_sampling_rate_estimate_ready = true;
+
+    // Resets the estimator variables.
+    sampleRateEstimatorResetAccumulator(sample_rate_estimator);
+  }
+}
diff --git a/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.h b/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.h
new file mode 100644
index 0000000..f506eea
--- /dev/null
+++ b/firmware/os/algos/calibration/sample_rate_estimator/sample_rate_estimator.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * [Sample Rate Estimator]
+ * This module periodically estimates the mean sampling rate of a uniformly
+ * sampled signal. Note, this estimator uses a floating point accumulator for
+ * the timing intervals. This trades-off some accumulation of finite precision
+ * error to enhance the range of estimated sampling rates and prevent overflow
+ * when an equivalent 32bit integer accumulator is used. In typical use cases
+ * (sample rates: 5Hz - 2kHz, num_intervals_to_collect 10 - 100), the sample
+ * rate accuracy is typically much better than 0.1Hz.
+ *
+ * FUNCTIONALITY:
+ * sampleRateEstimatorInit -- Initializes the estimator. Sets the number of time
+ *   intervals require to compute the sample rate mean, and the upper limit for
+ *   the acceptable time interval.
+ *
+ * new_sampling_rate_estimate_ready -- Check this boolean flag if polling for
+ *   new estimates is desired.
+ *
+ * sampleRateEstimatorGetHz -- Returns the latest sample rate estimate and
+ *   resets the polling flag.
+ *
+ * sampleRateEstimatorUpdate -- Processes new timestamp data and computes new
+ *   sample rate estimates.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SAMPLE_RATE_ESTIMATOR_SAMPLE_RATE_ESTIMATOR_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SAMPLE_RATE_ESTIMATOR_SAMPLE_RATE_ESTIMATOR_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Designates the value used to indicate an invalid sample rate estimate.
+#define SAMPLE_RATE_ESTIMATOR_INVALID_SAMPLE_RATE_HZ (-1.0f)
+
+// Sample rate estimator data structure.
+struct SampleRateEstimator {
+  // Used to compute sample intervals to estimate the sampling rate.
+  uint64_t last_timestamp_nanos;
+
+  // Accumulator used for computing the mean sampling interval.
+  float interval_accumulator_nanos;
+
+  // The upper limit on the acceptance of valid time intervals (in nanoseconds).
+  // Larger time deltas result in a reset of the interval accumulator.
+  float max_interval_nanos;
+
+  // The most recent mean sampling rate estimate.
+  float mean_sampling_rate_estimate_hz;
+
+  /*
+   * The targeted number of time intervals required for a new sample rate mean
+   * calculation.
+   *
+   * NOTE: Assuming that the time interval noise is independent and identically
+   * distributed and drawn from a zero-mean normal distribution with variance
+   * 'Sigma^2'. The expected noise reduction is related to the choice of
+   * 'num_intervals_to_collect' as:
+   *
+   *   Output RMS Noise = Sigma / sqrt(num_intervals_to_collect).
+   *
+   * Example, a value of 100 for a 100Hz signal would provide updates once every
+   * second and provide a 90% reduction (i.e., [1 - 1/sqrt(100)] * 100%) in time
+   * interval noise.
+   */
+  size_t num_intervals_to_collect;
+
+  // The number of time intervals currently collected.
+  size_t num_intervals_collected;
+
+  // Polling flag used to signal that a new estimate is ready. This flag is
+  // reset when 'sampleRateEstimatorGetHz' is called.
+  bool new_sampling_rate_estimate_ready;
+};
+
+// Initializes the sample rate estimator, sets the number of time intervals
+// for the mean sample rate calculation, and defines the tolerable gap in timing
+// data (in seconds).
+void sampleRateEstimatorInit(struct SampleRateEstimator* sample_rate_estimator,
+                             size_t num_intervals_to_collect,
+                             float max_interval_sec);
+
+// Rather than using a function to poll for new sample rate estimates, which
+// would incur an additional function call, simply check
+// 'new_sampling_rate_estimate_ready' directly.
+
+// Returns the most recent sampling rate estimate, and resets the
+// 'new_sampling_rate_estimate_ready' polling flag. Note, if polling is not
+// used, one may access the sample rate estimate directly from the struct and
+// avoid this function call.
+float sampleRateEstimatorGetHz(
+    struct SampleRateEstimator* sample_rate_estimator);
+
+// Takes in a new timestamp and updates the sampling rate estimator.
+void sampleRateEstimatorUpdate(
+    struct SampleRateEstimator* sample_rate_estimator,
+    uint64_t timestamp_nanos);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SAMPLE_RATE_ESTIMATOR_SAMPLE_RATE_ESTIMATOR_H_
diff --git a/firmware/os/algos/calibration/synchronizer/synchronizer.cc b/firmware/os/algos/calibration/synchronizer/synchronizer.cc
new file mode 100644
index 0000000..a485389
--- /dev/null
+++ b/firmware/os/algos/calibration/synchronizer/synchronizer.cc
@@ -0,0 +1,370 @@
+#include "calibration/synchronizer/synchronizer.h"
+
+#include "calibration/util/cal_log.h"
+
+namespace synchronizer {
+namespace {
+
+// Alias to simplify the access to SensorIndex.
+using SensorIndex = online_calibration::SensorIndex;
+
+// Seeks through data_to_interpolate_to and data_to_interpolate queues so that
+// first two entries of data_to_interpolate have timestamps that straddle the
+// timestamp of the first entry of data_to_interpolate_to. Leading queue items
+// are popped and discarded to perform seeking.
+//
+// Assumes both data_to_interpolate_to and data_to_interpolate sorted by
+// strictly increasing timestamp.
+//
+// Returns True if seeking was achieved. Returns false if seeking could not
+// be achieved, such is if either pointer was null or they contain data
+// streams which cannot be made to straddle in time.
+
+bool SeekStraddle(
+    chre::ArrayQueue<online_calibration::SensorData,
+                     Synchronizer::kInputBufferSize>* data_to_interpolate_to,
+    chre::ArrayQueue<online_calibration::SensorData,
+                     Synchronizer::kInputBufferSize>* data_to_interpolate) {
+  // If either is a null pointer, return false
+  if (data_to_interpolate_to == nullptr || data_to_interpolate == nullptr) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Unexpected null pointer during seek!");
+#endif
+    return false;
+  }
+
+  // If data_to_interpolate_to and data_to_interpolate are the same, no seeking.
+  if (reinterpret_cast<void*>(data_to_interpolate_to) ==
+      reinterpret_cast<void*>(data_to_interpolate)) {
+    return true;
+  }
+
+  // Check that queues are not empty and time min/max aren't impossible.
+  if (data_to_interpolate_to->empty() || data_to_interpolate->empty() ||
+      data_to_interpolate->front().timestamp_nanos >
+          data_to_interpolate_to->back().timestamp_nanos ||
+      data_to_interpolate->back().timestamp_nanos <
+          data_to_interpolate_to->front().timestamp_nanos) {
+    return false;
+  }
+
+  // Seek in data_to_interpolate_to until first entry of data_to_interpolate
+  // precedes first of data_to_interpolate_to.
+  while (!data_to_interpolate_to->empty() &&
+         data_to_interpolate->front().timestamp_nanos >
+             data_to_interpolate_to->front().timestamp_nanos) {
+    data_to_interpolate_to->pop();
+  }
+
+  // Check that data_to_interpolate_to still has at least one element.
+  if (data_to_interpolate_to->empty()) {
+    return false;
+  }
+
+  // Seek in data_to_interpolate until 1st and 2nd entries straddle first
+  // data_to_interpolate_to entry.
+  while (data_to_interpolate->size() > 1 &&
+         !((*data_to_interpolate)[0].timestamp_nanos <=
+               data_to_interpolate_to->front().timestamp_nanos &&
+           (*data_to_interpolate)[1].timestamp_nanos >=
+               data_to_interpolate_to->front().timestamp_nanos)) {
+    data_to_interpolate->pop();
+  }
+
+  // data_to_interpolate must either match exactly, or have >=2 elements to
+  // interpolate
+  if (data_to_interpolate->front().timestamp_nanos ==
+      data_to_interpolate_to->front().timestamp_nanos) {
+    return true;
+  } else if (data_to_interpolate->size() < 2) {
+    return false;
+  }
+
+  // Check that data_to_interpolate still has at least two elements and they
+  // straddle the first entry of data_to_interpolate_to.
+  if ((*data_to_interpolate)[0].timestamp_nanos >
+          data_to_interpolate_to->front().timestamp_nanos ||
+      (*data_to_interpolate)[1].timestamp_nanos <
+          data_to_interpolate_to->front().timestamp_nanos) {
+    return false;
+  }
+
+  return true;
+}
+// Performs linear interpolation for a single axis.
+float SingleAxisLinearInterpolation(float tb_minus_ta, float tc_minus_ta,
+                                    float va, float vc) {
+  return va + (vc - va) * (tb_minus_ta / tc_minus_ta);
+}
+
+// Interpolates data_to_interpolate using timestamps in
+// data_to_interpolate_to, writing to interpolated the resulting 3-axis
+// measurement vector and timestamp that matches a timestamp in
+// data_to_interpolate_to. For this to be possible, data_to_interpolate
+// must have at least two timestamps which straddle a timestamp in
+// data_to_interpolate_to.
+//
+// The input queues are assumed to have strictly increasing timestamps.
+//
+// First, this uses SeekStraddle() to pop off entries of each queue until the
+// first two entries of data_to_interpolate have timestamps that straddle the
+// timestamp of the first entry of data_to_interpolate_to. Then linear
+// interpolation proceeds and results are written to interpolated.
+//
+// Returns true if interpolation was possible. Returns false if any of the
+// inputs are nullptr or if interpolation was not possible given the
+// timestamps in the data queues.
+bool SeekAndInterpolateLinear(
+    chre::ArrayQueue<online_calibration::SensorData,
+                     Synchronizer::kInputBufferSize>* data_to_interpolate_to,
+    chre::ArrayQueue<online_calibration::SensorData,
+                     Synchronizer::kInputBufferSize>* data_to_interpolate,
+    online_calibration::SensorData* interpolated) {
+  // Seek until first two entries of data_to_interpolate straddle first entry of
+  // data_to_interpolate_to. Seek function also checks that
+  // data_to_interpolate_to and data_to_interpolate are not nullptr.
+  if (!SeekStraddle(data_to_interpolate_to, data_to_interpolate) ||
+      interpolated == nullptr) {
+    return false;
+  }
+
+  // Use some local variables to avoid repeated pointer dereferencing.
+  // data_to_interpolate_to and data_to_interpolate queues guaranteed by seek
+  // function to be non-empty.
+  online_calibration::SensorData data_to_interpolate_to_0 =
+      data_to_interpolate_to->front();
+  const online_calibration::SensorData data_to_interpolate_0 =
+      data_to_interpolate->front();
+  online_calibration::SensorData data_to_interpolate_1;
+
+  // Avoid floating point math if possible: check if timestamps match exactly.
+  if (data_to_interpolate_to_0.timestamp_nanos ==
+      data_to_interpolate_0.timestamp_nanos) {
+    *interpolated = data_to_interpolate_0;
+    return true;
+  } else if (data_to_interpolate->size() > 1) {
+    data_to_interpolate_1 = (*data_to_interpolate)[1];
+    if (data_to_interpolate_to_0.timestamp_nanos ==
+        data_to_interpolate_1.timestamp_nanos) {
+      *interpolated = data_to_interpolate_1;
+      return true;
+    }
+  } else {
+// Size was 1 and first entries didn't match, so interpolation impossible!
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Unexpected queue state while attempting interpolation!");
+#endif
+    return false;
+  }
+
+  // Linearly interpolate data_to_interpolate at data_to_interpolate_to's 0th
+  // entry timepoint. Interpolate vector vb between va, vc at time tb where
+  // ta < tb < tc.
+  const float tb_minus_ta =
+      static_cast<float>(data_to_interpolate_to_0.timestamp_nanos -
+                         data_to_interpolate_0.timestamp_nanos);
+  const float tc_minus_ta =
+      static_cast<float>(data_to_interpolate_1.timestamp_nanos -
+                         data_to_interpolate_0.timestamp_nanos);
+
+  if (data_to_interpolate_0.type ==
+          online_calibration::SensorType::kAccelerometerMps2 ||
+      data_to_interpolate_0.type ==
+          online_calibration::SensorType::kGyroscopeRps ||
+      data_to_interpolate_0.type ==
+          online_calibration::SensorType::kMagnetometerUt) {
+    // Perform linear interpolation for 3-axis sensor and populate the result
+    // struct.
+    interpolated->data[SensorIndex::kXAxis] = SingleAxisLinearInterpolation(
+        tb_minus_ta, tc_minus_ta,
+        data_to_interpolate_0.data[SensorIndex::kXAxis],
+        data_to_interpolate_1.data[SensorIndex::kXAxis]);
+    interpolated->data[SensorIndex::kYAxis] = SingleAxisLinearInterpolation(
+        tb_minus_ta, tc_minus_ta,
+        data_to_interpolate_0.data[SensorIndex::kYAxis],
+        data_to_interpolate_1.data[SensorIndex::kYAxis]);
+    interpolated->data[SensorIndex::kZAxis] = SingleAxisLinearInterpolation(
+        tb_minus_ta, tc_minus_ta,
+        data_to_interpolate_0.data[SensorIndex::kZAxis],
+        data_to_interpolate_1.data[SensorIndex::kZAxis]);
+    interpolated->timestamp_nanos = data_to_interpolate_to_0.timestamp_nanos;
+    interpolated->type = data_to_interpolate_0.type;
+    return true;
+  } else if (data_to_interpolate_0.type ==
+                 online_calibration::SensorType::kTemperatureCelsius ||
+             data_to_interpolate_0.type ==
+                 online_calibration::SensorType::kBarometerHpa) {
+    // Perform linear interpolation for 1-axis sensor and populate the result
+    // struct.
+    interpolated->data[SensorIndex::kSingleAxis] =
+        SingleAxisLinearInterpolation(
+            tb_minus_ta, tc_minus_ta,
+            data_to_interpolate_0.data[SensorIndex::kSingleAxis],
+            data_to_interpolate_1.data[SensorIndex::kSingleAxis]);
+    interpolated->timestamp_nanos = data_to_interpolate_to_0.timestamp_nanos;
+    interpolated->type = data_to_interpolate_0.type;
+    return true;
+  } else {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Invalid sensor_type in SeekAndInterpolateLinear");
+#endif
+    return false;
+  }
+}
+
+// Attempts to add a SensorData measurement to a deque of them, returning
+// whether the new measurement was strictly greater in time than the last
+// entry in the queue. If it was not, measurement is not added and false is
+// returned. This function is primarily for code compactness: several queues
+// of measurements are appended-to in this same manner.
+bool AddMeasurementToDeque(
+    const online_calibration::SensorData& measurement,
+    chre::ArrayQueue<online_calibration::SensorData,
+                     Synchronizer::kInputBufferSize>* mutable_deque) {
+  // Push measurement onto the measurement queue if sequential.
+  if (mutable_deque->empty() ||
+      (!mutable_deque->empty() &&
+       measurement.timestamp_nanos > mutable_deque->back().timestamp_nanos)) {
+    mutable_deque->push(measurement);
+    return true;
+  } else {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG(
+        "[SYNCHRONIZER WARNING]",
+        "measurement timestamp: %lu was not strictly greater than existing"
+        "queued timestamps.",
+        measurement.timestamp_nanos);
+#endif
+    return false;
+  }
+}
+}  // namespace
+
+Synchronizer::Synchronizer()
+    : Synchronizer(online_calibration::SensorType::kGyroscopeRps,
+                   online_calibration::SensorType::kAccelerometerMps2) {}
+
+Synchronizer::Synchronizer(
+    const online_calibration::SensorType& sensor_to_interpolate_to,
+    const online_calibration::SensorType& sensor_to_interpolate) {
+  if (sensor_to_interpolate_to == sensor_to_interpolate) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Sensor_types are equal! Using default settings!");
+#endif
+    sensor_to_interpolate_to_ = online_calibration::SensorType::kGyroscopeRps;
+    sensor_to_interpolate_ = online_calibration::SensorType::kAccelerometerMps2;
+  } else {
+    // Setting sensors.
+    sensor_to_interpolate_to_ = sensor_to_interpolate_to;
+    sensor_to_interpolate_ = sensor_to_interpolate;
+  }
+}
+
+bool Synchronizer::SetDataToInterpolateToSensorType(
+    const online_calibration::SensorType& sensor_type) {
+  if (sensor_type == sensor_to_interpolate_) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Sensor_type in SetDataToInterpolateToSensorType is "
+                  "already used!");
+#endif
+    return false;
+  }
+  if (sensor_type == online_calibration::SensorType::kUndefined) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Invalid sensor_type in SetDataToInterpolateToSensorType");
+#endif
+    return false;
+  }
+  sensor_to_interpolate_to_ = sensor_type;
+  return true;
+}
+
+bool Synchronizer::SetDataToInterpolateSensorType(
+    const online_calibration::SensorType& sensor_type) {
+  if (sensor_type == sensor_to_interpolate_to_) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG(
+        "[SYNCHRONIZER WARNING]",
+        "Sensor_type in SetDataToInterpolateSensorType is already used!");
+#endif
+    return false;
+  }
+  if (sensor_type == online_calibration::SensorType::kUndefined) {
+#ifdef SYNCHRONIZER_DBG_ENABLED
+    CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                  "Invalid sensor_type in SetDataToInterpolateSensorType");
+#endif
+    return false;
+  }
+  sensor_to_interpolate_ = sensor_type;
+  return true;
+}
+
+void Synchronizer::AddMeasurement(const online_calibration::SensorData& meas) {
+  online_calibration::SensorType sensor_type = meas.type;
+  if (sensor_type == sensor_to_interpolate_to_) {
+    if (input_sensor_to_interpolate_to_.full()) {
+      CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                    "input_sensor_to_interpolate_to queue is full! "
+                    "Removing front element from queue");
+      input_sensor_to_interpolate_to_.pop();
+    }
+    if (AddMeasurementToDeque(meas, &input_sensor_to_interpolate_to_)) {
+      // Add succeeded. Process applicable measurement queue.
+      ProcessMeasurementQueues();
+    }
+  }
+  if (sensor_type == sensor_to_interpolate_) {
+    if (input_sensor_to_interpolate_.full()) {
+      CAL_DEBUG_LOG("[SYNCHRONIZER WARNING]",
+                    "input_sensor_to_interpolate queue is full !"
+                    "Removing front element from queue");
+      input_sensor_to_interpolate_.pop();
+    }
+    if (AddMeasurementToDeque(meas, &input_sensor_to_interpolate_)) {
+      // Add succeeded. Process applicable measurement queue.
+      ProcessMeasurementQueues();
+    }
+  }
+}
+
+bool Synchronizer::ConsumeSynchronizedMeasurementQueues(
+    /*first sensor*/ online_calibration::SensorData* sensor_to_interpolate_to,
+    /*second sensor*/ online_calibration::SensorData* sensor_to_interpolate) {
+  if (!synchronized_first_and_second_queues_.empty()) {
+    *sensor_to_interpolate_to =
+        synchronized_first_and_second_queues_.front().first;
+    *sensor_to_interpolate =
+        synchronized_first_and_second_queues_.front().second;
+    synchronized_first_and_second_queues_.pop();
+    return true;
+  }
+  return false;
+}
+
+void Synchronizer::ProcessMeasurementQueues() {
+  // Results of interpolation written here.
+  online_calibration::SensorData interpolated;
+
+  // Process the queues, interpolating and queuing up measurements.
+  while (SeekAndInterpolateLinear(&input_sensor_to_interpolate_to_,
+                                  &input_sensor_to_interpolate_,
+                                  &interpolated)) {
+    synchronized_first_and_second_queues_.push(
+        std::pair<online_calibration::SensorData,
+                  online_calibration::SensorData>(
+            input_sensor_to_interpolate_to_.front(), interpolated));
+
+    // Pop off data_to_interpolate_to measurement used to create this sample.
+    // data_to_interpolate will be cleaned up next seek.
+    input_sensor_to_interpolate_to_.pop();
+  }
+}
+
+}  // namespace synchronizer
diff --git a/firmware/os/algos/calibration/synchronizer/synchronizer.h b/firmware/os/algos/calibration/synchronizer/synchronizer.h
new file mode 100644
index 0000000..1a0ef4c
--- /dev/null
+++ b/firmware/os/algos/calibration/synchronizer/synchronizer.h
@@ -0,0 +1,100 @@
+/*
+ * This is a sensor data synchronizer module that interpolates buffers (queues)
+ * of streamed measurements with respect to a user-selected input master stream
+ * to set the timebase. To perform synchronization, a sensor (sensor_data.h)
+ * is selected to define the resulting synchronized time axis. The second
+ * selected sensor is interpolated to the first sensor's timescale. This
+ * produces a synchronized queue of measurement pairs.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SYNCHRONIZER_SYNCHRONIZER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SYNCHRONIZER_SYNCHRONIZER_H_
+
+#include "calibration/online_calibration/common_data/sensor_data.h"
+#include "chre/util/array_queue.h"
+
+namespace synchronizer {
+
+/*
+ * Buffers measurements and synchronizes them using interpolation.
+ *
+ * Processes input streams of sensors and queues up output streams.
+ */
+class Synchronizer {
+ public:
+  // Input buffer size = 400 gives 1 second of buffer for a input sensor data
+  // rate of 400 Hz.
+  static constexpr int kInputBufferSize = 400;
+  // The output buffer size can store 10 sample, hist code has to ensure to
+  // grab the data before overflow.
+  static constexpr int kOutputBufferSize = 10;
+
+  Synchronizer();
+
+  Synchronizer(const online_calibration::SensorType& sensor_to_interpolate_to,
+               const online_calibration::SensorType& sensor_to_interpolate);
+
+  // Sets the sensor (first) to which the second sensor is interpolated to.
+  // Hence this sensor timebase is the master of the synchronized data stream.
+  // Notice that "sensor_to_interpolate_to_" cannot be equal
+  // "sensor_to_interpolate_", so this function cannot synchronize two accel's
+  // to each other.
+  // TODO(b/70861106): Change interface to a master slave approach.
+  // Sensors available (sensor_data.h).
+  bool SetDataToInterpolateToSensorType(
+      const online_calibration::SensorType& sensor_type);
+
+  // Sets the sensor (second) which will be interpolated. Notice that
+  // "sensor_to_interpolate_to_" cannot be equal "sensor_to_interpolate_".
+  // Sensors available (sensor_data.h).
+  bool SetDataToInterpolateSensorType(
+      const online_calibration::SensorType& sensor_type);
+
+  // Adds measurements. Uses the type in SensorData to identify
+  // sensor_to_interpolate and sensor_to_interpolate_to sensors.
+  void AddMeasurement(const online_calibration::SensorData& meas);
+
+  // Processes the synchronization queues in chronological order. Processing
+  // involves popping off each queue element and passing it to the specified
+  // function target of corresponding sensor type.
+  //
+  // Returns true if at least one element is in the sensor queue, and populates
+  // the synchronzied sensor data structs.
+  bool ConsumeSynchronizedMeasurementQueues(
+      /*sensor_to_interpolate_to*/
+      online_calibration::SensorData* sensor_to_interpolate_to,
+      /*sensor_to_interpolate*/
+      online_calibration::SensorData* sensor_to_interpolate);
+
+ private:
+  // Attempts to pair up "sensor_to_interpolate_to" (first) and
+  // "sensor_to_interpolate" (second)  measurements buffered by the
+  // AddMeasurement() function, interpolating one using the other as a
+  // data_to_interpolate_to timescale if necessary.
+  //
+  // If interpolated measurements are generated, they are queued in this
+  // function.
+
+  void ProcessMeasurementQueues();
+
+  // Queue of synchronized measurements.
+  chre::ArrayQueue<
+      std::pair<online_calibration::SensorData, online_calibration::SensorData>,
+      kOutputBufferSize>
+      synchronized_first_and_second_queues_;
+
+  // Enums set which measurement defines the data_to_interpolate_to (first)
+  // timebase, against which sensor_to_interpolate is interpolated (second).
+  online_calibration::SensorType sensor_to_interpolate_to_;
+  online_calibration::SensorType sensor_to_interpolate_;
+
+  // Queues of time-ordered measurements for interpolation within this class.
+  chre::ArrayQueue<online_calibration::SensorData, kInputBufferSize>
+      input_sensor_to_interpolate_to_;
+  chre::ArrayQueue<online_calibration::SensorData, kInputBufferSize>
+      input_sensor_to_interpolate_;
+};
+
+}  // namespace synchronizer
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_SYNCHRONIZER_SYNCHRONIZER_H_
diff --git a/firmware/os/algos/calibration/util/cal_log.h b/firmware/os/algos/calibration/util/cal_log.h
index 1bd80fd..593c087 100644
--- a/firmware/os/algos/calibration/util/cal_log.h
+++ b/firmware/os/algos/calibration/util/cal_log.h
@@ -22,6 +22,7 @@
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_UTIL_CAL_LOG_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_UTIL_CAL_LOG_H_
 
+// clang-format off
 #ifdef GCC_DEBUG_LOG
 # include <stdio.h>
 # define CAL_DEBUG_LOG(tag, fmt, ...) \
@@ -40,6 +41,7 @@
 # define CAL_DEBUG_LOG(tag, fmt, ...) \
    chreLog(CHRE_LOG_INFO, "%s " fmt, tag, ##__VA_ARGS__)
 #endif  // GCC_DEBUG_LOG
+// clang-format on
 
 #ifdef __cplusplus
 extern "C" {
@@ -50,9 +52,10 @@
 #define CAL_FLOOR(x) ((int)(x) - ((x) < (int)(x)))  // NOLINT
 
 // Macro used to print floating point numbers with a specified number of digits.
-#define CAL_ENCODE_FLOAT(x, num_digits) \
-  ((x < 0) ? "-" : ""),                 \
-  (int)CAL_FLOOR(fabsf(x)), (int)((fabsf(x) - CAL_FLOOR(fabsf(x))) * powf(10, num_digits))  // NOLINT
+#define CAL_ENCODE_FLOAT(x, num_digits)           \
+  ((x < 0) ? "-" : ""), (int)CAL_FLOOR(fabsf(x)), \
+      (int)((fabsf(x) - CAL_FLOOR(fabsf(x))) *    \
+            powf(10, num_digits))  // NOLINT
 
 // Helper definitions for CAL_ENCODE_FLOAT to specify the print format with
 // desired significant digits.
diff --git a/firmware/os/algos/common/math/kasa.c b/firmware/os/algos/common/math/kasa.c
new file mode 100644
index 0000000..a24a31b
--- /dev/null
+++ b/firmware/os/algos/common/math/kasa.c
@@ -0,0 +1,120 @@
+#include "common/math/kasa.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "common/math/mat.h"
+
+void kasaReset(struct KasaFit *kasa) {
+  kasa->acc_x = kasa->acc_y = kasa->acc_z = kasa->acc_w = 0.0f;
+  kasa->acc_xx = kasa->acc_xy = kasa->acc_xz = kasa->acc_xw = 0.0f;
+  kasa->acc_yy = kasa->acc_yz = kasa->acc_yw = 0.0f;
+  kasa->acc_zz = kasa->acc_zw = 0.0f;
+  kasa->nsamples = 0;
+}
+
+void kasaInit(struct KasaFit *kasa) { kasaReset(kasa); }
+
+void kasaAccumulate(struct KasaFit *kasa, float x, float y, float z) {
+  float w = x * x + y * y + z * z;
+
+  kasa->acc_x += x;
+  kasa->acc_y += y;
+  kasa->acc_z += z;
+  kasa->acc_w += w;
+
+  kasa->acc_xx += x * x;
+  kasa->acc_xy += x * y;
+  kasa->acc_xz += x * z;
+  kasa->acc_xw += x * w;
+
+  kasa->acc_yy += y * y;
+  kasa->acc_yz += y * z;
+  kasa->acc_yw += y * w;
+
+  kasa->acc_zz += z * z;
+  kasa->acc_zw += z * w;
+
+  kasa->nsamples += 1;
+}
+
+bool kasaNormalize(struct KasaFit *kasa) {
+  if (kasa->nsamples == 0) {
+    return false;
+  }
+
+  float inv = 1.0f / kasa->nsamples;
+
+  kasa->acc_x *= inv;
+  kasa->acc_y *= inv;
+  kasa->acc_z *= inv;
+  kasa->acc_w *= inv;
+
+  kasa->acc_xx *= inv;
+  kasa->acc_xy *= inv;
+  kasa->acc_xz *= inv;
+  kasa->acc_xw *= inv;
+
+  kasa->acc_yy *= inv;
+  kasa->acc_yz *= inv;
+  kasa->acc_yw *= inv;
+
+  kasa->acc_zz *= inv;
+  kasa->acc_zw *= inv;
+
+  return true;
+}
+
+int kasaFit(struct KasaFit *kasa, struct Vec3 *bias, float *radius,
+            float max_fit, float min_fit) {
+  //    A    *   out   =    b
+  // (4 x 4)   (4 x 1)   (4 x 1)
+  struct Mat44 A;
+  A.elem[0][0] = kasa->acc_xx;
+  A.elem[0][1] = kasa->acc_xy;
+  A.elem[0][2] = kasa->acc_xz;
+  A.elem[0][3] = kasa->acc_x;
+  A.elem[1][0] = kasa->acc_xy;
+  A.elem[1][1] = kasa->acc_yy;
+  A.elem[1][2] = kasa->acc_yz;
+  A.elem[1][3] = kasa->acc_y;
+  A.elem[2][0] = kasa->acc_xz;
+  A.elem[2][1] = kasa->acc_yz;
+  A.elem[2][2] = kasa->acc_zz;
+  A.elem[2][3] = kasa->acc_z;
+  A.elem[3][0] = kasa->acc_x;
+  A.elem[3][1] = kasa->acc_y;
+  A.elem[3][2] = kasa->acc_z;
+  A.elem[3][3] = 1.0f;
+
+  struct Vec4 b;
+  initVec4(&b, -kasa->acc_xw, -kasa->acc_yw, -kasa->acc_zw, -kasa->acc_w);
+
+  struct Size4 pivot;
+  mat44DecomposeLup(&A, &pivot);
+
+  struct Vec4 out;
+  mat44Solve(&A, &out, &b, &pivot);
+
+  // sphere: (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2
+  //
+  // xc = -out[0] / 2, yc = -out[1] / 2, zc = -out[2] / 2
+  // r = sqrt(xc^2 + yc^2 + zc^2 - out[3])
+
+  struct Vec3 v;
+  initVec3(&v, out.x, out.y, out.z);
+  vec3ScalarMul(&v, -0.5f);
+
+  float r_square = vec3Dot(&v, &v) - out.w;
+  float r = (r_square > 0) ? sqrtf(r_square) : 0;
+
+  initVec3(bias, v.x, v.y, v.z);
+  *radius = r;
+
+  int success = 0;
+  if (r > min_fit && r < max_fit) {
+    success = 1;
+  }
+
+  return success;
+}
diff --git a/firmware/os/algos/common/math/kasa.h b/firmware/os/algos/common/math/kasa.h
new file mode 100644
index 0000000..e9652d6
--- /dev/null
+++ b/firmware/os/algos/common/math/kasa.h
@@ -0,0 +1,54 @@
+/*
+ * This module provides a data structure, initialization, and fit
+ * routine for algorithms that use the Kasa method for determining the
+ * 3-dimensional offset vector from a set of points on a sphere.
+ *
+ * Reference: I. Kåsa, "A circle fitting procedure and its error analysis," in
+ * IEEE Transactions on Instrumentation and Measurement, vol. IM-25, no. 1, pp.
+ * 8-14, March 1976.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_KASA_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_KASA_H_
+
+#include <stdbool.h>
+
+#include "common/math/vec.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct KasaFit {
+  float acc_x, acc_y, acc_z, acc_w;
+  float acc_xx, acc_xy, acc_xz, acc_xw;
+  float acc_yy, acc_yz, acc_yw, acc_zz, acc_zw;
+  size_t nsamples;
+};
+
+// Resets the KasaFit data structure (sets all variables to zero).
+void kasaReset(struct KasaFit *kasa);
+
+// Initializes the KasaFit data structure.
+void kasaInit(struct KasaFit *kasa);
+
+// Accumulates the Kasa acc_** variables with the input vector [x, y, z], and
+// updates the number of samples.
+void kasaAccumulate(struct KasaFit *kasa, float x, float y, float z);
+
+// Normalizes the Kasa acc_** variables. Returns 'false' if the number of
+// samples is zero, otherwise 'true'.
+bool kasaNormalize(struct KasaFit *kasa);
+
+// Uses the Kasa sphere-fit method to extract a 'bias' estimate (centroid) for
+// the best-fit sphere using the normal equations, and the sphere's 'radius'.
+// Returns '1' if the radius of the fit sphere is within the bounds
+// (min_fit, max_fit), otherwise '0'.
+int kasaFit(struct KasaFit *kasa, struct Vec3 *bias, float *radius,
+            float max_fit, float min_fit);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_KASA_H_
diff --git a/firmware/os/algos/common/math/levenberg_marquardt.h b/firmware/os/algos/common/math/levenberg_marquardt.h
index c01d2eb..d62308d 100644
--- a/firmware/os/algos/common/math/levenberg_marquardt.h
+++ b/firmware/os/algos/common/math/levenberg_marquardt.h
@@ -109,7 +109,7 @@
 
 // Initializes LM solver with provided parameters and error function.
 void lmSolverInit(struct LmSolver *solver, const struct LmParams *params,
-                  ResidualAndJacobianFunction error_func);
+                  ResidualAndJacobianFunction func);
 
 void lmSolverDestroy(struct LmSolver *solver);
 
@@ -133,7 +133,7 @@
  */
 enum LmStatus lmSolverSolve(struct LmSolver *solver, const float *initial_state,
                             void *f_data, size_t state_dim, size_t meas_dim,
-                            float *est_state);
+                            float *state);
 
 ////////////////////////// TEST UTILITIES ////////////////////////////////////
 // This function is exposed here for testing purposes only.
diff --git a/firmware/os/algos/common/math/macros.h b/firmware/os/algos/common/math/macros.h
index 5c06e24..1d785d1 100644
--- a/firmware/os/algos/common/math/macros.h
+++ b/firmware/os/algos/common/math/macros.h
@@ -16,6 +16,8 @@
 
 // This file contains helper macros and definitions.
 
+#include <stdint.h>
+
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
 
@@ -26,18 +28,28 @@
 #define NANO_ABS(x) ((x) > 0 ? (x) : -(x))
 #define NANO_MAX(a, b) ((a) > (b)) ? (a) : (b)
 #define NANO_MIN(a, b) ((a) < (b)) ? (a) : (b)
+#define SIGMOID(x) (1 / (1 + expf(-x)))
 
 // Timestamp conversion macros.
 #ifdef __cplusplus
-#define MSEC_TO_NANOS(x) (static_cast<uint64_t>(x) * 1000000)
+#define MSEC_TO_NANOS(x) (static_cast<uint64_t>(x * UINT64_C(1000000)))
 #else
-#define MSEC_TO_NANOS(x) ((uint64_t)(x) * 1000000)  // NOLINT
+#define MSEC_TO_NANOS(x) ((uint64_t)(x * UINT64_C(1000000)))  // NOLINT
 #endif
 
-#define SEC_TO_NANOS(x)  MSEC_TO_NANOS(x * 1000)
-#define MIN_TO_NANOS(x)  SEC_TO_NANOS(x * 60)
-#define HRS_TO_NANOS(x)  MIN_TO_NANOS(x * 60)
-#define DAYS_TO_NANOS(x) HRS_TO_NANOS(x * 24)
+#define SEC_TO_NANOS(x)  MSEC_TO_NANOS(x * UINT64_C(1000))
+#define MIN_TO_NANOS(x)  SEC_TO_NANOS (x * UINT64_C(60))
+#define HRS_TO_NANOS(x)  MIN_TO_NANOS (x * UINT64_C(60))
+#define DAYS_TO_NANOS(x) HRS_TO_NANOS (x * UINT64_C(24))
+
+// Sample rate to period conversion.
+#ifdef __cplusplus
+#define HZ_TO_PERIOD_NANOS(hz) \
+  (SEC_TO_NANOS(1024) / (static_cast<uint64_t>(hz * 1024)))
+#else
+#define HZ_TO_PERIOD_NANOS(hz) \
+  (SEC_TO_NANOS(1024) / ((uint64_t)(hz * 1024)))  // NOLINT
+#endif
 
 // Unit conversion: nanoseconds to seconds.
 #define NANOS_TO_SEC (1.0e-9f)
@@ -57,4 +69,26 @@
 #define NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(t1, t2, t_delta) \
   (((t1) >= (t2) + (t_delta)) || ((t1) < (t2)))
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This conversion function may be necessary for embedded hardware that can't
+// cast a uint64_t to a float directly. This conversion function was taken from:
+// /third_party/contexthub/firmware/core/floatRt.c
+static inline float floatFromUint64(uint64_t v) {
+  uint32_t hi = v >> 32;
+  uint32_t lo = (uint32_t) v;
+
+  if (!hi) {  // This is very fast for cases where 'v' fits into a uint32_t.
+    return (float)lo;
+  } else {
+    return ((float)hi) * 4294967296.0f + (float)lo;
+  }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
 #endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
diff --git a/firmware/os/algos/util/array.h b/firmware/os/algos/util/array.h
new file mode 100644
index 0000000..9658be4
--- /dev/null
+++ b/firmware/os/algos/util/array.h
@@ -0,0 +1,6 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_ARRAY_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_ARRAY_H_
+
+#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a)[0]))
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_ARRAY_H_
diff --git a/firmware/os/algos/util/nano_assert.h b/firmware/os/algos/util/nano_assert.h
index da77749..cb3286e 100644
--- a/firmware/os/algos/util/nano_assert.h
+++ b/firmware/os/algos/util/nano_assert.h
@@ -1,7 +1,7 @@
 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_NANO_ASSERT_H_
 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_NANO_ASSERT_H_
 
-#if defined(__NANOHUB__) || defined(_OS_BUILD_)
+#ifndef GOOGLE3
 
 // For external nanoapps (__NANOHUB__ defined), use SRC_FILENAME provided
 // by the build system, which has the directory stripped. But allow the
@@ -32,7 +32,7 @@
 #include <assert.h>
 #define ASSERT_IMPL(x) assert(x)
 
-#endif
+#endif  // GOOGLE3
 
 #ifndef ASSERT
 #ifdef NANO_ASSERT_ENABLED
diff --git a/firmware/os/algos/util/nano_log.h b/firmware/os/algos/util/nano_log.h
new file mode 100644
index 0000000..8ec8043
--- /dev/null
+++ b/firmware/os/algos/util/nano_log.h
@@ -0,0 +1,13 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_NANO_LOG_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_NANO_LOG_H_
+
+#ifdef GOOGLE3
+#include <stdio.h>
+
+// ignore log level argument
+#define LOG_FUNC(level, ...) printf(__VA_ARGS__)
+#endif
+
+#include "third_party/contexthub/nanoapps/util/log/log.h"
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_UTIL_NANO_LOG_H_