/*
 * 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.
 */

#include "calibration/gyroscope/gyro_cal.h"

#include <float.h>
#include <inttypes.h>
#include <math.h>
#include <string.h>

#ifdef GYRO_CAL_DBG_ENABLED
#include "calibration/util/cal_log.h"
#endif  // GYRO_CAL_DBG_ENABLED

#include "common/math/macros.h"

/////// DEFINITIONS AND MACROS ///////////////////////////////////////

// Maximum gyro bias correction (should be set based on expected max bias
// of the given sensor).
#define MAX_GYRO_BIAS (0.2f)  // [rad/sec]

// Watchdog timeout value (5 seconds). Monitors dropouts in sensor data and
// resets when exceeded.
#define GYRO_WATCHDOG_TIMEOUT_NANOS (SEC_TO_NANOS(5))

#ifdef GYRO_CAL_DBG_ENABLED
// The time value used to throttle debug messaging.
#define GYROCAL_WAIT_TIME_NANOS (MSEC_TO_NANOS(100))

// 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

/////// FORWARD DECLARATIONS /////////////////////////////////////////

static void deviceStillnessCheck(struct GyroCal* gyro_cal,
                                 uint64_t sample_time_nanos);

static void computeGyroCal(struct GyroCal* gyro_cal,
                           uint64_t calibration_time_nanos);

static void checkWatchdog(struct GyroCal* gyro_cal, uint64_t sample_time_nanos);

// Data tracker command enumeration.
enum GyroCalTrackerCommand {
  DO_RESET = 0,    // Resets the local data used for data tracking.
  DO_UPDATE_DATA,  // Updates the local tracking data.
  DO_STORE_DATA,   // Stores intermediate results for later recall.
  DO_EVALUATE      // Computes and provides the results of the gate function.
};

/*
 * Updates the temperature min/max and mean during the stillness period. Returns
 * 'true' if the min and max temperature values exceed the range set by
 * 'temperature_delta_limit_celsius'.
 *
 * INPUTS:
 *   gyro_cal:     Pointer to the GyroCal data structure.
 *   temperature_celsius:  New temperature sample to include.
 *   do_this:      Command enumerator that controls function behavior:
 */
static bool gyroTemperatureStatsTracker(struct GyroCal* gyro_cal,
                                        float temperature_celsius,
                                        enum GyroCalTrackerCommand do_this);

/*
 * Tracks the minimum and maximum gyroscope stillness window means.
 * Returns 'true' when the difference between gyroscope min and max window
 * means are outside the range set by 'stillness_mean_delta_limit'.
 *
 * INPUTS:
 *   gyro_cal:     Pointer to the GyroCal data structure.
 *   do_this:      Command enumerator that controls function behavior.
 */
static bool gyroStillMeanTracker(struct GyroCal* gyro_cal,
                                 enum GyroCalTrackerCommand do_this);

#ifdef GYRO_CAL_DBG_ENABLED
// Defines the type of debug data to print.
enum DebugPrintData {
  OFFSET = 0,
  STILLNESS_DATA,
  SAMPLE_RATE_AND_TEMPERATURE,
  GYRO_MINMAX_STILLNESS_MEAN,
  ACCEL_STATS,
  GYRO_STATS,
  MAG_STATS,
  ACCEL_STATS_TUNING,
  GYRO_STATS_TUNING,
  MAG_STATS_TUNING
};

// Updates the information used for debug printouts.
static void gyroCalUpdateDebug(struct GyroCal* gyro_cal);

// Helper function for printing out common debug data.
static void gyroCalDebugPrintData(const struct GyroCal* gyro_cal,
                                  char* debug_tag,
                                  enum DebugPrintData print_data);
#endif  // GYRO_CAL_DBG_ENABLED

/////// FUNCTION DEFINITIONS /////////////////////////////////////////

// Initialize the gyro calibration data structure.
void gyroCalInit(struct GyroCal* gyro_cal,
                 const struct GyroCalParameters* parameters) {
  // Clear gyro_cal structure memory.
  memset(gyro_cal, 0, sizeof(struct GyroCal));

  // Initialize the stillness detectors.
  // 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,
                   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 = 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 = 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 = 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 = 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
  // valid end-time is set from the first gyro timestamp received.
  gyro_cal->stillness_win_endtime_nanos = 0;

  // Gyro calibrations will be applied (see, gyroCalRemoveBias()).
  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 = parameters->stillness_mean_delta_limit;

  // Sets the min/max temperature delta limit for the stillness period.
  gyro_cal->temperature_delta_limit_celsius =
      parameters->temperature_delta_limit_celsius;

  // Ensures that the data tracking functionality is reset.
  gyroStillMeanTracker(gyro_cal, DO_RESET);
  gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);

#ifdef GYRO_CAL_DBG_ENABLED
  if (gyro_cal->gyro_calibration_enable) {
    CAL_DEBUG_LOG("[GYRO_CAL:INIT]", "Online gyroscope calibration ENABLED.");
  } else {
    CAL_DEBUG_LOG("[GYRO_CAL:INIT]", "Online gyroscope calibration DISABLED.");
  }

  // 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
}

// 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,
                    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, 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]",
                "Offset|Temp|Time [mDPS|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
                ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
                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),
                calibration_time_nanos);
#endif  // GYRO_CAL_DBG_ENABLED
}

// Remove bias from a gyro measurement [rad/sec].
void gyroCalRemoveBias(struct GyroCal* gyro_cal, float xi, float yi, float zi,
                       float* xo, float* yo, float* zo) {
  if (gyro_cal->gyro_calibration_enable) {
    *xo = xi - gyro_cal->bias_x;
    *yo = yi - gyro_cal->bias_y;
    *zo = zi - gyro_cal->bias_z;
  }
}

// Returns true when a new gyro calibration is available.
bool gyroCalNewBiasAvailable(struct GyroCal* gyro_cal) {
  bool new_gyro_cal_available =
      (gyro_cal->gyro_calibration_enable && gyro_cal->new_gyro_cal_available);

  // Clear the flag.
  gyro_cal->new_gyro_cal_available = false;

  return new_gyro_cal_available;
}

// Update the gyro calibration with gyro data [rad/sec].
void gyroCalUpdateGyro(struct GyroCal* gyro_cal, uint64_t sample_time_nanos,
                       float x, float y, float z, float temperature_celsius) {
  // Make sure that a valid window end-time is set, and start the watchdog
  // timer.
  if (gyro_cal->stillness_win_endtime_nanos <= 0) {
    gyro_cal->stillness_win_endtime_nanos =
        sample_time_nanos + gyro_cal->window_time_duration_nanos;

    // Start the watchdog timer.
    gyro_cal->gyro_watchdog_start_nanos = sample_time_nanos;
  }

  // Update the temperature statistics.
  gyroTemperatureStatsTracker(gyro_cal, temperature_celsius, DO_UPDATE_DATA);

#ifdef GYRO_CAL_DBG_ENABLED
  // Update the gyro sampling rate estimate.
  sampleRateEstimatorUpdate(&gyro_cal->debug_gyro_cal.sample_rate_estimator,
                            sample_time_nanos);
#endif  // GYRO_CAL_DBG_ENABLED

  // Pass gyro data to stillness detector
  gyroStillDetUpdate(&gyro_cal->gyro_stillness_detect,
                     gyro_cal->stillness_win_endtime_nanos, sample_time_nanos,
                     x, y, z);

  // Perform a device stillness check, set next window end-time, and
  // possibly do a gyro bias calibration and stillness detector reset.
  deviceStillnessCheck(gyro_cal, sample_time_nanos);
}

// Update the gyro calibration with mag data [micro Tesla].
void gyroCalUpdateMag(struct GyroCal* gyro_cal, uint64_t sample_time_nanos,
                      float x, float y, float z) {
  // Pass magnetometer data to stillness detector.
  gyroStillDetUpdate(&gyro_cal->mag_stillness_detect,
                     gyro_cal->stillness_win_endtime_nanos, sample_time_nanos,
                     x, y, z);

  // Received a magnetometer sample; incorporate it into detection.
  gyro_cal->using_mag_sensor = true;

  // Perform a device stillness check, set next window end-time, and
  // possibly do a gyro bias calibration and stillness detector reset.
  deviceStillnessCheck(gyro_cal, sample_time_nanos);
}

// Update the gyro calibration with accel data [m/sec^2].
void gyroCalUpdateAccel(struct GyroCal* gyro_cal, uint64_t sample_time_nanos,
                        float x, float y, float z) {
  // Pass accelerometer data to stillnesss detector.
  gyroStillDetUpdate(&gyro_cal->accel_stillness_detect,
                     gyro_cal->stillness_win_endtime_nanos, sample_time_nanos,
                     x, y, z);

  // Perform a device stillness check, set next window end-time, and
  // possibly do a gyro bias calibration and stillness detector reset.
  deviceStillnessCheck(gyro_cal, sample_time_nanos);
}

// TODO: Consider breaking this function up to improve readability.
// Checks the state of all stillness detectors to determine
// whether the device is "still".
void deviceStillnessCheck(struct GyroCal* gyro_cal,
                          uint64_t sample_time_nanos) {
  bool stillness_duration_exceeded = false;
  bool stillness_duration_too_short = false;
  bool min_max_temp_exceeded = false;
  bool mean_not_stable = false;
  bool device_is_still = false;
  float conf_not_rot = 0;
  float conf_not_accel = 0;
  float conf_still = 0;

  // Check the watchdog timer.
  checkWatchdog(gyro_cal, sample_time_nanos);

  // Is there enough data to do a stillness calculation?
  if ((!gyro_cal->mag_stillness_detect.stillness_window_ready &&
       gyro_cal->using_mag_sensor) ||
      !gyro_cal->accel_stillness_detect.stillness_window_ready ||
      !gyro_cal->gyro_stillness_detect.stillness_window_ready) {
    return;  // Not yet, wait for more data.
  }

  // Set the next window end-time for the stillness detectors.
  gyro_cal->stillness_win_endtime_nanos =
      sample_time_nanos + gyro_cal->window_time_duration_nanos;

  // Update the confidence scores for all sensors.
  gyroStillDetCompute(&gyro_cal->accel_stillness_detect);
  gyroStillDetCompute(&gyro_cal->gyro_stillness_detect);
  if (gyro_cal->using_mag_sensor) {
    gyroStillDetCompute(&gyro_cal->mag_stillness_detect);
  } else {
    // Not using magnetometer, force stillness confidence to 100%.
    gyro_cal->mag_stillness_detect.stillness_confidence = 1.0f;
  }

  // Updates the mean tracker data.
  gyroStillMeanTracker(gyro_cal, DO_UPDATE_DATA);

  // Determine motion confidence scores (rotation, accelerating, and stillness).
  conf_not_rot = gyro_cal->gyro_stillness_detect.stillness_confidence *
                 gyro_cal->mag_stillness_detect.stillness_confidence;
  conf_not_accel = gyro_cal->accel_stillness_detect.stillness_confidence;
  conf_still = conf_not_rot * conf_not_accel;

  // Evaluate the mean and temperature gate functions.
  mean_not_stable = gyroStillMeanTracker(gyro_cal, DO_EVALUATE);
  min_max_temp_exceeded =
      gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_EVALUATE);

  // Determines if the device is currently still.
  device_is_still = (conf_still > gyro_cal->stillness_threshold) &&
                    !mean_not_stable && !min_max_temp_exceeded;

  if (device_is_still) {
    // Device is "still" logic:
    // If not previously still, then record the start time.
    // If stillness period is too long, then do a calibration.
    // Otherwise, continue collecting stillness data.

    // If device was not previously still, set new start timestamp.
    if (!gyro_cal->prev_still) {
      // Record the starting timestamp of the current stillness window.
      // This enables the calculation of total duration of the stillness period.
      gyro_cal->start_still_time_nanos =
          gyro_cal->gyro_stillness_detect.window_start_time;
    }

    // Check to see if current stillness period exceeds the desired limit.
    stillness_duration_exceeded =
        (gyro_cal->gyro_stillness_detect.last_sample_time >=
         gyro_cal->start_still_time_nanos + gyro_cal->max_still_duration_nanos);

    // Track the new stillness mean and temperature data.
    gyroStillMeanTracker(gyro_cal, DO_STORE_DATA);
    gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_STORE_DATA);

    if (stillness_duration_exceeded) {
      // The current stillness has gone too long. Do a calibration with the
      // current data and reset.

      // Updates the gyro bias estimate with the current window data and
      // resets the stats.
      gyroStillDetReset(&gyro_cal->accel_stillness_detect,
                        /*reset_stats=*/true);
      gyroStillDetReset(&gyro_cal->gyro_stillness_detect, /*reset_stats=*/true);
      gyroStillDetReset(&gyro_cal->mag_stillness_detect, /*reset_stats=*/true);

      // Resets the local calculations because the stillness period is over.
      gyroStillMeanTracker(gyro_cal, DO_RESET);
      gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);

      // Computes a new gyro offset estimate.
      computeGyroCal(gyro_cal,
                     gyro_cal->gyro_stillness_detect.last_sample_time);

      // Update stillness flag. Force the start of a new stillness period.
      gyro_cal->prev_still = false;
    } else {
      // Continue collecting stillness data.

      // Extend the stillness period.
      gyroStillDetReset(&gyro_cal->accel_stillness_detect,
                        /*reset_stats=*/false);
      gyroStillDetReset(&gyro_cal->gyro_stillness_detect,
                        /*reset_stats=*/false);
      gyroStillDetReset(&gyro_cal->mag_stillness_detect, /*reset_stats=*/false);

      // Update the stillness flag.
      gyro_cal->prev_still = true;
    }
  } else {
    // Device is NOT still; motion detected.

    // If device was previously still and the total stillness duration is not
    // "too short", then do a calibration with the data accumulated thus far.
    stillness_duration_too_short =
        (gyro_cal->gyro_stillness_detect.window_start_time <
         gyro_cal->start_still_time_nanos + gyro_cal->min_still_duration_nanos);

    if (gyro_cal->prev_still && !stillness_duration_too_short) {
      computeGyroCal(gyro_cal,
                     gyro_cal->gyro_stillness_detect.window_start_time);
    }

    // Reset the stillness detectors and the stats.
    gyroStillDetReset(&gyro_cal->accel_stillness_detect, /*reset_stats=*/true);
    gyroStillDetReset(&gyro_cal->gyro_stillness_detect, /*reset_stats=*/true);
    gyroStillDetReset(&gyro_cal->mag_stillness_detect, /*reset_stats=*/true);

    // Resets the temperature and sensor mean data.
    gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);
    gyroStillMeanTracker(gyro_cal, DO_RESET);

    // Update stillness flag.
    gyro_cal->prev_still = false;
  }

  // Reset the watchdog timer after we have processed data.
  gyro_cal->gyro_watchdog_start_nanos = sample_time_nanos;
}

// 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 &&
        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_z < MAX_GYRO_BIAS &&
        gyro_cal->gyro_stillness_detect.prev_mean_z > -MAX_GYRO_BIAS)) {
#ifdef GYRO_CAL_DBG_ENABLED
    CAL_DEBUG_LOG(
        "[GYRO_CAL:REJECT]",
        "Offset|Temp|Time [mDPS|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
        ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
        CAL_ENCODE_FLOAT(
            gyro_cal->gyro_stillness_detect.prev_mean_x * RAD_TO_MDEG, 3),
        CAL_ENCODE_FLOAT(
            gyro_cal->gyro_stillness_detect.prev_mean_y * RAD_TO_MDEG, 3),
        CAL_ENCODE_FLOAT(
            gyro_cal->gyro_stillness_detect.prev_mean_z * RAD_TO_MDEG, 3),
        CAL_ENCODE_FLOAT(gyro_cal->temperature_mean_celsius, 3),
        calibration_time_nanos);
#endif  // GYRO_CAL_DBG_ENABLED

    // Outside of range. Ignore, reset, and continue.
    return;
  }

  // Record the new gyro bias offset calibration.
  gyro_cal->bias_x = gyro_cal->gyro_stillness_detect.prev_mean_x;
  gyro_cal->bias_y = gyro_cal->gyro_stillness_detect.prev_mean_y;
  gyro_cal->bias_z = gyro_cal->gyro_stillness_detect.prev_mean_z;

  // Store the calibration temperature (using the mean temperature over the
  // "stillness" period).
  gyro_cal->bias_temperature_celsius = gyro_cal->temperature_mean_celsius;

  // Store the calibration time stamp.
  gyro_cal->calibration_time_nanos = calibration_time_nanos;

  // Record the final stillness confidence.
  gyro_cal->stillness_confidence =
      gyro_cal->gyro_stillness_detect.prev_stillness_confidence *
      gyro_cal->accel_stillness_detect.prev_stillness_confidence *
      gyro_cal->mag_stillness_detect.prev_stillness_confidence;

  // Set flag to indicate a new gyro calibration value is available.
  gyro_cal->new_gyro_cal_available = true;

#ifdef GYRO_CAL_DBG_ENABLED
  // Increment the total count of calibration updates.
  gyro_cal->debug_calibration_count++;

  // Update the calibration debug information and trigger a printout.
  gyroCalUpdateDebug(gyro_cal);
#endif
}

// Check for a watchdog timeout condition.
void checkWatchdog(struct GyroCal* gyro_cal, uint64_t sample_time_nanos) {
  bool watchdog_timeout;

  // Check for initialization of the watchdog time (=0).
  if (gyro_cal->gyro_watchdog_start_nanos <= 0) {
    return;
  }

  // Checks for the following watchdog timeout conditions:
  //    i.  The current timestamp has exceeded the allowed watchdog duration.
  //    ii. A timestamp was received that has jumped backwards by more than the
  //        allowed watchdog duration (e.g., timestamp clock roll-over).
  watchdog_timeout =
      (sample_time_nanos > gyro_cal->gyro_watchdog_timeout_duration_nanos +
                               gyro_cal->gyro_watchdog_start_nanos) ||
      (sample_time_nanos + gyro_cal->gyro_watchdog_timeout_duration_nanos <
       gyro_cal->gyro_watchdog_start_nanos);

  // If a timeout occurred then reset to known good state.
  if (watchdog_timeout) {
#ifdef GYRO_CAL_DBG_ENABLED
    gyro_cal->debug_watchdog_count++;
    if (sample_time_nanos < gyro_cal->gyro_watchdog_start_nanos) {
      CAL_DEBUG_LOG("[GYRO_CAL:WATCHDOG]",
                    "Total#, Timestamp | Delta [nsec]: %zu, %" PRIu64
                    ", -%" PRIu64,
                    gyro_cal->debug_watchdog_count, sample_time_nanos,
                    gyro_cal->gyro_watchdog_start_nanos - sample_time_nanos);
    } else {
      CAL_DEBUG_LOG("[GYRO_CAL:WATCHDOG]",
                    "Total#, Timestamp | Delta  [nsec]: %zu, %" PRIu64
                    ", %" PRIu64,
                    gyro_cal->debug_watchdog_count, sample_time_nanos,
                    sample_time_nanos - gyro_cal->gyro_watchdog_start_nanos);
    }
#endif  // GYRO_CAL_DBG_ENABLED

    // Reset stillness detectors and restart data capture.
    gyroStillDetReset(&gyro_cal->accel_stillness_detect, /*reset_stats=*/true);
    gyroStillDetReset(&gyro_cal->gyro_stillness_detect, /*reset_stats=*/true);
    gyroStillDetReset(&gyro_cal->mag_stillness_detect, /*reset_stats=*/true);

    // Resets the temperature and sensor mean data.
    gyroTemperatureStatsTracker(gyro_cal, 0.0f, DO_RESET);
    gyroStillMeanTracker(gyro_cal, DO_RESET);

    // Resets the stillness window end-time.
    gyro_cal->stillness_win_endtime_nanos = 0;

    // Force stillness confidence to zero.
    gyro_cal->accel_stillness_detect.prev_stillness_confidence = 0;
    gyro_cal->gyro_stillness_detect.prev_stillness_confidence = 0;
    gyro_cal->mag_stillness_detect.prev_stillness_confidence = 0;
    gyro_cal->stillness_confidence = 0;
    gyro_cal->prev_still = false;

    // If there are no magnetometer samples being received then
    // operate the calibration algorithm without this sensor.
    if (!gyro_cal->mag_stillness_detect.stillness_window_ready &&
        gyro_cal->using_mag_sensor) {
      gyro_cal->using_mag_sensor = false;
    }

    // Assert watchdog timeout flags.
    gyro_cal->gyro_watchdog_start_nanos = 0;
  }
}

// TODO -- Combine the following two functions into one or consider
// implementing a separate helper module for tracking the temperature and mean
// statistics.
bool gyroTemperatureStatsTracker(struct GyroCal* gyro_cal,
                                 float temperature_celsius,
                                 enum GyroCalTrackerCommand do_this) {
  bool min_max_temp_exceeded = false;

  switch (do_this) {
    case DO_RESET:
      // Resets the mean accumulator.
      gyro_cal->temperature_mean_tracker.num_points = 0;
      gyro_cal->temperature_mean_tracker.mean_accumulator = 0.0f;

      // Initializes the min/max temperatures values.
      gyro_cal->temperature_mean_tracker.temperature_min_celsius = FLT_MAX;
      gyro_cal->temperature_mean_tracker.temperature_max_celsius = -FLT_MAX;
      break;

    case DO_UPDATE_DATA:
      // Does the mean accumulation.
      gyro_cal->temperature_mean_tracker.mean_accumulator +=
          temperature_celsius;
      gyro_cal->temperature_mean_tracker.num_points++;

      // Tracks the min, max, and latest temperature values.
      gyro_cal->temperature_mean_tracker.latest_temperature_celsius =
          temperature_celsius;
      if (gyro_cal->temperature_mean_tracker.temperature_min_celsius >
          temperature_celsius) {
        gyro_cal->temperature_mean_tracker.temperature_min_celsius =
            temperature_celsius;
      }
      if (gyro_cal->temperature_mean_tracker.temperature_max_celsius <
          temperature_celsius) {
        gyro_cal->temperature_mean_tracker.temperature_max_celsius =
            temperature_celsius;
      }
      break;

    case DO_STORE_DATA:
      // Store the most recent temperature statistics data to the GyroCal data
      // structure. This functionality allows previous results to be recalled
      // when the device suddenly becomes "not still".
      if (gyro_cal->temperature_mean_tracker.num_points > 0) {
        gyro_cal->temperature_mean_celsius =
            gyro_cal->temperature_mean_tracker.mean_accumulator /
            gyro_cal->temperature_mean_tracker.num_points;
      } else {
        gyro_cal->temperature_mean_celsius =
            gyro_cal->temperature_mean_tracker.latest_temperature_celsius;
#ifdef GYRO_CAL_DBG_ENABLED
        CAL_DEBUG_LOG("[GYRO_CAL:TEMP_GATE]",
                      "Insufficient statistics (num_points = 0), using latest "
                      "measured temperature as the mean value.");
#endif  // GYRO_CAL_DBG_ENABLED
      }
#ifdef GYRO_CAL_DBG_ENABLED
      // Records the min/max and mean temperature values for debug purposes.
      gyro_cal->debug_gyro_cal.temperature_mean_celsius =
          gyro_cal->temperature_mean_celsius;
      gyro_cal->debug_gyro_cal.temperature_min_celsius =
          gyro_cal->temperature_mean_tracker.temperature_min_celsius;
      gyro_cal->debug_gyro_cal.temperature_max_celsius =
          gyro_cal->temperature_mean_tracker.temperature_max_celsius;
#endif
      break;

    case DO_EVALUATE:
      // Determines if the min/max delta exceeded the set limit.
      if (gyro_cal->temperature_mean_tracker.num_points > 0) {
        min_max_temp_exceeded =
            (gyro_cal->temperature_mean_tracker.temperature_max_celsius -
             gyro_cal->temperature_mean_tracker.temperature_min_celsius) >
            gyro_cal->temperature_delta_limit_celsius;

#ifdef GYRO_CAL_DBG_ENABLED
        if (min_max_temp_exceeded) {
          CAL_DEBUG_LOG(
              "[GYRO_CAL:TEMP_GATE]",
              "Exceeded the max temperature variation during stillness.");
        }
#endif  // GYRO_CAL_DBG_ENABLED
      }
      break;

    default:
      break;
  }

  return min_max_temp_exceeded;
}

bool gyroStillMeanTracker(struct GyroCal* gyro_cal,
                          enum GyroCalTrackerCommand do_this) {
  bool mean_not_stable = false;

  switch (do_this) {
    case DO_RESET:
      // Resets the min/max window mean values to a default value.
      for (size_t i = 0; i < 3; i++) {
        gyro_cal->window_mean_tracker.gyro_winmean_min[i] = FLT_MAX;
        gyro_cal->window_mean_tracker.gyro_winmean_max[i] = -FLT_MAX;
      }
      break;

    case DO_UPDATE_DATA:
      // Computes the min/max window mean values.
      if (gyro_cal->window_mean_tracker.gyro_winmean_min[0] >
          gyro_cal->gyro_stillness_detect.win_mean_x) {
        gyro_cal->window_mean_tracker.gyro_winmean_min[0] =
            gyro_cal->gyro_stillness_detect.win_mean_x;
      }
      if (gyro_cal->window_mean_tracker.gyro_winmean_max[0] <
          gyro_cal->gyro_stillness_detect.win_mean_x) {
        gyro_cal->window_mean_tracker.gyro_winmean_max[0] =
            gyro_cal->gyro_stillness_detect.win_mean_x;
      }

      if (gyro_cal->window_mean_tracker.gyro_winmean_min[1] >
          gyro_cal->gyro_stillness_detect.win_mean_y) {
        gyro_cal->window_mean_tracker.gyro_winmean_min[1] =
            gyro_cal->gyro_stillness_detect.win_mean_y;
      }
      if (gyro_cal->window_mean_tracker.gyro_winmean_max[1] <
          gyro_cal->gyro_stillness_detect.win_mean_y) {
        gyro_cal->window_mean_tracker.gyro_winmean_max[1] =
            gyro_cal->gyro_stillness_detect.win_mean_y;
      }

      if (gyro_cal->window_mean_tracker.gyro_winmean_min[2] >
          gyro_cal->gyro_stillness_detect.win_mean_z) {
        gyro_cal->window_mean_tracker.gyro_winmean_min[2] =
            gyro_cal->gyro_stillness_detect.win_mean_z;
      }
      if (gyro_cal->window_mean_tracker.gyro_winmean_max[2] <
          gyro_cal->gyro_stillness_detect.win_mean_z) {
        gyro_cal->window_mean_tracker.gyro_winmean_max[2] =
            gyro_cal->gyro_stillness_detect.win_mean_z;
      }
      break;

    case DO_STORE_DATA:
      // Store the most recent "stillness" mean data to the GyroCal data
      // structure. This functionality allows previous results to be recalled
      // when the device suddenly becomes "not still".
      memcpy(gyro_cal->gyro_winmean_min,
             gyro_cal->window_mean_tracker.gyro_winmean_min,
             sizeof(gyro_cal->window_mean_tracker.gyro_winmean_min));
      memcpy(gyro_cal->gyro_winmean_max,
             gyro_cal->window_mean_tracker.gyro_winmean_max,
             sizeof(gyro_cal->window_mean_tracker.gyro_winmean_max));
      break;

    case DO_EVALUATE:
      // Performs the stability check and returns the 'true' if the difference
      // between min/max window mean value is outside the stable range.
      for (size_t i = 0; i < 3; i++) {
        mean_not_stable |= (gyro_cal->window_mean_tracker.gyro_winmean_max[i] -
                            gyro_cal->window_mean_tracker.gyro_winmean_min[i]) >
                           gyro_cal->stillness_mean_delta_limit;
      }
#ifdef GYRO_CAL_DBG_ENABLED
      if (mean_not_stable) {
        CAL_DEBUG_LOG(
            "[GYRO_CAL:MEAN_STABILITY_GATE]",
            "Variation Limit|Delta [mDPS]: " CAL_FORMAT_3DIGITS
            " | " CAL_FORMAT_3DIGITS_TRIPLET,
            CAL_ENCODE_FLOAT(gyro_cal->stillness_mean_delta_limit * RAD_TO_MDEG,
                             3),
            CAL_ENCODE_FLOAT(
                (gyro_cal->window_mean_tracker.gyro_winmean_max[0] -
                 gyro_cal->window_mean_tracker.gyro_winmean_min[0]) *
                    RAD_TO_MDEG,
                3),
            CAL_ENCODE_FLOAT(
                (gyro_cal->window_mean_tracker.gyro_winmean_max[1] -
                 gyro_cal->window_mean_tracker.gyro_winmean_min[1]) *
                    RAD_TO_MDEG,
                3),
            CAL_ENCODE_FLOAT(
                (gyro_cal->window_mean_tracker.gyro_winmean_max[2] -
                 gyro_cal->window_mean_tracker.gyro_winmean_min[2]) *
                    RAD_TO_MDEG,
                3));
      }
#endif  // GYRO_CAL_DBG_ENABLED
      break;

    default:
      break;
  }

  return mean_not_stable;
}

#ifdef GYRO_CAL_DBG_ENABLED
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
  // being reported).
  if (gyro_cal->debug_state != GYRO_IDLE) {
    return;
  }

  // Probability of stillness (acc, rot, still), duration, timestamp.
  gyro_cal->debug_gyro_cal.accel_stillness_conf =
      gyro_cal->accel_stillness_detect.prev_stillness_confidence;
  gyro_cal->debug_gyro_cal.gyro_stillness_conf =
      gyro_cal->gyro_stillness_detect.prev_stillness_confidence;
  gyro_cal->debug_gyro_cal.mag_stillness_conf =
      gyro_cal->mag_stillness_detect.prev_stillness_confidence;

  // Magnetometer usage.
  gyro_cal->debug_gyro_cal.using_mag_sensor = gyro_cal->using_mag_sensor;

  // Stillness start, stop, and duration times.
  gyro_cal->debug_gyro_cal.start_still_time_nanos =
      gyro_cal->start_still_time_nanos;
  gyro_cal->debug_gyro_cal.end_still_time_nanos =
      gyro_cal->calibration_time_nanos;
  gyro_cal->debug_gyro_cal.stillness_duration_nanos =
      gyro_cal->calibration_time_nanos - gyro_cal->start_still_time_nanos;

  // Records the current calibration values.
  gyro_cal->debug_gyro_cal.calibration[0] = gyro_cal->bias_x;
  gyro_cal->debug_gyro_cal.calibration[1] = gyro_cal->bias_y;
  gyro_cal->debug_gyro_cal.calibration[2] = gyro_cal->bias_z;

  // 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));
  memcpy(gyro_cal->debug_gyro_cal.gyro_winmean_max, gyro_cal->gyro_winmean_max,
         sizeof(gyro_cal->gyro_winmean_max));

  // Records the previous stillness window means.
  gyro_cal->debug_gyro_cal.accel_mean[0] =
      gyro_cal->accel_stillness_detect.prev_mean_x;
  gyro_cal->debug_gyro_cal.accel_mean[1] =
      gyro_cal->accel_stillness_detect.prev_mean_y;
  gyro_cal->debug_gyro_cal.accel_mean[2] =
      gyro_cal->accel_stillness_detect.prev_mean_z;

  gyro_cal->debug_gyro_cal.gyro_mean[0] =
      gyro_cal->gyro_stillness_detect.prev_mean_x;
  gyro_cal->debug_gyro_cal.gyro_mean[1] =
      gyro_cal->gyro_stillness_detect.prev_mean_y;
  gyro_cal->debug_gyro_cal.gyro_mean[2] =
      gyro_cal->gyro_stillness_detect.prev_mean_z;

  gyro_cal->debug_gyro_cal.mag_mean[0] =
      gyro_cal->mag_stillness_detect.prev_mean_x;
  gyro_cal->debug_gyro_cal.mag_mean[1] =
      gyro_cal->mag_stillness_detect.prev_mean_y;
  gyro_cal->debug_gyro_cal.mag_mean[2] =
      gyro_cal->mag_stillness_detect.prev_mean_z;

  // Records the variance data.
  // NOTE: These statistics include the final captured window, which may be
  // outside of the "stillness" period. Therefore, these values may exceed the
  // stillness thresholds.
  gyro_cal->debug_gyro_cal.accel_var[0] =
      gyro_cal->accel_stillness_detect.win_var_x;
  gyro_cal->debug_gyro_cal.accel_var[1] =
      gyro_cal->accel_stillness_detect.win_var_y;
  gyro_cal->debug_gyro_cal.accel_var[2] =
      gyro_cal->accel_stillness_detect.win_var_z;

  gyro_cal->debug_gyro_cal.gyro_var[0] =
      gyro_cal->gyro_stillness_detect.win_var_x;
  gyro_cal->debug_gyro_cal.gyro_var[1] =
      gyro_cal->gyro_stillness_detect.win_var_y;
  gyro_cal->debug_gyro_cal.gyro_var[2] =
      gyro_cal->gyro_stillness_detect.win_var_z;

  gyro_cal->debug_gyro_cal.mag_var[0] =
      gyro_cal->mag_stillness_detect.win_var_x;
  gyro_cal->debug_gyro_cal.mag_var[1] =
      gyro_cal->mag_stillness_detect.win_var_y;
  gyro_cal->debug_gyro_cal.mag_var[2] =
      gyro_cal->mag_stillness_detect.win_var_z;

  // Trigger a printout of the debug information.
  gyro_cal->debug_print_trigger = true;
}

void gyroCalDebugPrintData(const struct GyroCal* gyro_cal, char* debug_tag,
                           enum DebugPrintData print_data) {
  // Prints out the desired debug data.
  float mag_data;
  switch (print_data) {
    case OFFSET:
      CAL_DEBUG_LOG(
          debug_tag,
          "Cal#|Offset|Temp|Time [mDPS|C|nsec]: "
          "%zu, " CAL_FORMAT_3DIGITS_TRIPLET ", " CAL_FORMAT_3DIGITS
          ", %" PRIu64,
          gyro_cal->debug_calibration_count,
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.calibration[0] * RAD_TO_MDEG, 3),
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.calibration[1] * RAD_TO_MDEG, 3),
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.calibration[2] * RAD_TO_MDEG, 3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.temperature_mean_celsius,
                           3),
          gyro_cal->debug_gyro_cal.end_still_time_nanos);
      break;

    case STILLNESS_DATA:
      mag_data = (gyro_cal->debug_gyro_cal.using_mag_sensor)
                     ? gyro_cal->debug_gyro_cal.mag_stillness_conf
                     : -1.0f;  // Signals that magnetometer was not used.
      CAL_DEBUG_LOG(
          debug_tag,
          "Cal#|Stillness|Confidence [nsec]: %zu, "
          "%" PRIu64 ", " CAL_FORMAT_3DIGITS_TRIPLET,
          gyro_cal->debug_calibration_count,
          gyro_cal->debug_gyro_cal.end_still_time_nanos -
              gyro_cal->debug_gyro_cal.start_still_time_nanos,
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_stillness_conf, 3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_stillness_conf, 3),
          CAL_ENCODE_FLOAT(mag_data, 3));
      break;

    case SAMPLE_RATE_AND_TEMPERATURE:
      CAL_DEBUG_LOG(
          debug_tag,
          "Cal#|Mean|Min|Max|Delta|Sample Rate [C|Hz]: "
          "%zu, " CAL_FORMAT_3DIGITS_TRIPLET ", " CAL_FORMAT_3DIGITS
          ", " CAL_FORMAT_3DIGITS,
          gyro_cal->debug_calibration_count,
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.temperature_mean_celsius,
                           3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.temperature_min_celsius, 3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.temperature_max_celsius, 3),
          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.sample_rate_estimator
                               .mean_sampling_rate_estimate_hz,
                           3));
      break;

    case GYRO_MINMAX_STILLNESS_MEAN:
      CAL_DEBUG_LOG(
          debug_tag,
          "Cal#|Gyro Peak Stillness Variation [mDPS]: "
          "%zu, " CAL_FORMAT_3DIGITS_TRIPLET,
          gyro_cal->debug_calibration_count,
          CAL_ENCODE_FLOAT((gyro_cal->debug_gyro_cal.gyro_winmean_max[0] -
                            gyro_cal->debug_gyro_cal.gyro_winmean_min[0]) *
                               RAD_TO_MDEG,
                           3),
          CAL_ENCODE_FLOAT((gyro_cal->debug_gyro_cal.gyro_winmean_max[1] -
                            gyro_cal->debug_gyro_cal.gyro_winmean_min[1]) *
                               RAD_TO_MDEG,
                           3),
          CAL_ENCODE_FLOAT((gyro_cal->debug_gyro_cal.gyro_winmean_max[2] -
                            gyro_cal->debug_gyro_cal.gyro_winmean_min[2]) *
                               RAD_TO_MDEG,
                           3));
      break;

    case ACCEL_STATS:
      CAL_DEBUG_LOG(debug_tag,
                    "Cal#|Accel Mean|Var [m/sec^2|(m/sec^2)^2]: "
                    "%zu, " CAL_FORMAT_3DIGITS_TRIPLET
                    ", " CAL_FORMAT_6DIGITS_TRIPLET,
                    gyro_cal->debug_calibration_count,
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_mean[0], 3),
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_mean[1], 3),
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_mean[2], 3),
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_var[0], 6),
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_var[1], 6),
                    CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.accel_var[2], 6));
      break;

    case GYRO_STATS:
      CAL_DEBUG_LOG(
          debug_tag,
          "Cal#|Gyro Mean|Var [mDPS|mDPS^2]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET
          ", " CAL_FORMAT_3DIGITS_TRIPLET,
          gyro_cal->debug_calibration_count,
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_mean[0] * RAD_TO_MDEG,
                           3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_mean[1] * RAD_TO_MDEG,
                           3),
          CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.gyro_mean[2] * RAD_TO_MDEG,
                           3),
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.gyro_var[0] * RAD_TO_MDEG * RAD_TO_MDEG,
              3),
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.gyro_var[1] * RAD_TO_MDEG * RAD_TO_MDEG,
              3),
          CAL_ENCODE_FLOAT(
              gyro_cal->debug_gyro_cal.gyro_var[2] * RAD_TO_MDEG * RAD_TO_MDEG,
              3));
      break;

    case MAG_STATS:
      if (gyro_cal->debug_gyro_cal.using_mag_sensor) {
        CAL_DEBUG_LOG(
            debug_tag,
            "Cal#|Mag Mean|Var [uT|uT^2]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET
            ", " CAL_FORMAT_6DIGITS_TRIPLET,
            gyro_cal->debug_calibration_count,
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[0], 3),
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[1], 3),
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_mean[2], 3),
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[0], 6),
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[1], 6),
            CAL_ENCODE_FLOAT(gyro_cal->debug_gyro_cal.mag_var[2], 6));
      } else {
        CAL_DEBUG_LOG(debug_tag,
                      "Cal#|Mag Mean|Var [uT|uT^2]: %zu, 0, 0, 0, -1.0, -1.0, "
                      "-1.0",
                      gyro_cal->debug_calibration_count);
      }
      break;

    default:
      break;
  }
}

void gyroCalDebugPrint(struct GyroCal* gyro_cal, uint64_t timestamp_nanos) {
  // This is a state machine that controls the reporting out of debug data.
  switch (gyro_cal->debug_state) {
    case GYRO_IDLE:
      // Wait for a trigger and start the debug printout sequence.
      if (gyro_cal->debug_print_trigger) {
        CAL_DEBUG_LOG(GYROCAL_REPORT_TAG, "");
        CAL_DEBUG_LOG(GYROCAL_REPORT_TAG, "Debug Version: %s",
                      GYROCAL_DEBUG_VERSION_STRING);
        gyro_cal->debug_print_trigger = false;  // Resets trigger.
        gyro_cal->debug_state = GYRO_PRINT_OFFSET;
      } else {
        gyro_cal->debug_state = GYRO_IDLE;
      }
      break;

    case GYRO_WAIT_STATE:
      // This helps throttle the print statements.
      if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(timestamp_nanos,
                                                   gyro_cal->wait_timer_nanos,
                                                   GYROCAL_WAIT_TIME_NANOS)) {
        gyro_cal->debug_state = gyro_cal->next_state;
      }
      break;

    case GYRO_PRINT_OFFSET:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG, OFFSET);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state = GYRO_PRINT_STILLNESS_DATA;  // Sets the next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;  // First, go to wait state.
      break;

    case GYRO_PRINT_STILLNESS_DATA:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG, STILLNESS_DATA);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state =
          GYRO_PRINT_SAMPLE_RATE_AND_TEMPERATURE;  // Sets next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;     // First, go to wait state.
      break;

    case GYRO_PRINT_SAMPLE_RATE_AND_TEMPERATURE:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG,
                            SAMPLE_RATE_AND_TEMPERATURE);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state =
          GYRO_PRINT_GYRO_MINMAX_STILLNESS_MEAN;  // Sets next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;    // First, go to wait state.
      break;

    case GYRO_PRINT_GYRO_MINMAX_STILLNESS_MEAN:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG,
                            GYRO_MINMAX_STILLNESS_MEAN);
      gyro_cal->wait_timer_nanos = timestamp_nanos;   // Starts the wait timer.
      gyro_cal->next_state = GYRO_PRINT_ACCEL_STATS;  // Sets the next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;  // First, go to wait state.
      break;

    case GYRO_PRINT_ACCEL_STATS:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG, ACCEL_STATS);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state = GYRO_PRINT_GYRO_STATS;  // Sets the next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
      break;

    case GYRO_PRINT_GYRO_STATS:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG, GYRO_STATS);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state = GYRO_PRINT_MAG_STATS;   // Sets the next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
      break;

    case GYRO_PRINT_MAG_STATS:
      gyroCalDebugPrintData(gyro_cal, GYROCAL_REPORT_TAG, MAG_STATS);
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->next_state = GYRO_IDLE;              // Sets the next state.
      gyro_cal->debug_state = GYRO_WAIT_STATE;       // First, go to wait state.
      break;

    default:
      // Sends this state machine to its idle state.
      gyro_cal->wait_timer_nanos = timestamp_nanos;  // Starts the wait timer.
      gyro_cal->debug_state = GYRO_IDLE;             // Go to idle state.
  }
}
#endif  // GYRO_CAL_DBG_ENABLED
