• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "calibration/over_temp/over_temp_cal.h"
18 
19 #include <float.h>
20 #include <math.h>
21 #include <string.h>
22 
23 #include "calibration/util/cal_log.h"
24 #include "common/math/vec.h"
25 #include "util/nano_assert.h"
26 
27 /////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
28 
29 // Rate-limits the search for the nearest offset estimate to every 2 seconds.
30 #define OVERTEMPCAL_NEAREST_NANOS (2000000000)
31 
32 // Rate-limits the check of old data to every 2 hours.
33 #define OVERTEMPCAL_STALE_CHECK_TIME_NANOS (7200000000000)
34 
35 // Value used to check whether OTC model parameters are near zero.
36 #define OTC_MODELDATA_NEAR_ZERO_TOL (1e-7f)  // [rad/sec]
37 
38 #ifdef OVERTEMPCAL_DBG_ENABLED
39 // A debug version label to help with tracking results.
40 #define OVERTEMPCAL_DEBUG_VERSION_STRING "[Apr 05, 2017]"
41 
42 // The time value used to throttle debug messaging.
43 #define OVERTEMPCAL_WAIT_TIME_NANOS (300000000)
44 
45 // Debug log tag string used to identify debug report output data.
46 #define OVERTEMPCAL_REPORT_TAG "[OVER_TEMP_CAL:REPORT]"
47 
48 // Converts units of radians to milli-degrees.
49 #define RAD_TO_MILLI_DEGREES (float)(1e3f * 180.0f / NANO_PI)
50 
51 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
52 static const char  kDebugAxisLabel[3] = "XYZ";
53 #endif  // OVERTEMPCAL_DBG_ENABLED
54 
55 /////// FORWARD DECLARATIONS //////////////////////////////////////////////////
56 
57 // Updates the model estimate data nearest to the sensor's temperature.
58 static void setNearestEstimate(struct OverTempCal *over_temp_cal,
59                               const float *offset, float offset_temp_celsius,
60                               uint64_t timestamp_nanos);
61 
62 /*
63  * Determines if a new over-temperature model fit should be performed, and then
64  * updates the model as needed.
65  *
66  * INPUTS:
67  *   over_temp_cal:    Over-temp data structure.
68  *   timestamp_nanos:  Current timestamp for the model update.
69  */
70 static void computeModelUpdate(struct OverTempCal *over_temp_cal,
71                                uint64_t timestamp_nanos);
72 
73 /*
74  * Searches 'model_data' for the sensor offset estimate closest to the current
75  * temperature. Sets the 'nearest_offset' pointer to the result.
76  */
77 static void findNearestEstimate(struct OverTempCal *over_temp_cal);
78 
79 /*
80  * Provides the current over-temperature compensated offset vector.
81  *
82  * INPUTS:
83  *   over_temp_cal:    Over-temp data structure.
84  *   timestamp_nanos:  The current system timestamp.
85  * OUTPUTS:
86  *   compensated_offset: Temperature compensated offset estimate array.
87  *   compensated_offset_temperature_celsius: Compensated offset temperature.
88  *
89  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
90  */
91 static void getCalOffset(struct OverTempCal *over_temp_cal,
92                          uint64_t timestamp_nanos,
93                          float *compensated_offset_temperature_celsius,
94                          float *compensated_offset);
95 
96 /*
97  * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the
98  * drift-compromised data). Returns 'true' if any data was removed.
99  */
100 static bool removeStaleModelData(struct OverTempCal *over_temp_cal,
101                                  uint64_t timestamp_nanos);
102 
103 /*
104  * Removes the offset estimates from 'model_data' at index, 'model_index'.
105  * Returns 'true' if data was removed.
106  */
107 static bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
108                                    size_t model_index);
109 
110 /*
111  * Since it may take a while for an empty model to build up enough data to start
112  * producing new model parameter updates, the model collection can be
113  * jump-started by using the new model parameters to insert fake data in place
114  * of actual sensor offset data.
115  */
116 static bool jumpStartModelData(struct OverTempCal *over_temp_cal);
117 
118 /*
119  * Provides updated model parameters for the over-temperature model data.
120  *
121  * INPUTS:
122  *   over_temp_cal:    Over-temp data structure.
123  * OUTPUTS:
124  *   temp_sensitivity: Updated modeled temperature sensitivity (array).
125  *   sensor_intercept: Updated model intercept (array).
126  *
127  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
128  *
129  * Reference: "Comparing two ways to fit a line to data", John D. Cook.
130  * http://www.johndcook.com/blog/2008/10/20/comparing-two-ways-to-fit-a-line-to-data/
131  */
132 static void updateModel(const struct OverTempCal *over_temp_cal,
133                         float *temp_sensitivity, float *sensor_intercept);
134 
135 /*
136  * Checks new offset estimates to determine if they could be an outlier that
137  * should be rejected. Operates on a per-axis basis determined by 'axis_index'.
138  *
139  * INPUTS:
140  *   over_temp_cal:    Over-temp data structure.
141  *   offset:           Offset array.
142  *   axis_index:       Index of the axis to check (0=x, 1=y, 2=z).
143  *
144  * Returns 'true' if the deviation of the offset value from the linear model
145  * exceeds 'max_error_limit'.
146  */
147 static bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
148                          size_t axis_index, float temperature_celsius);
149 
150 // Sets the OTC model parameters to an "initialized" state.
resetOtcLinearModel(struct OverTempCal * over_temp_cal)151 static void resetOtcLinearModel(struct OverTempCal *over_temp_cal) {
152   ASSERT_NOT_NULL(over_temp_cal);
153 
154   // Sets the temperature sensitivity model parameters to
155   // OTC_INITIAL_SENSITIVITY to indicate that the model is in an "initial"
156   // state.
157   over_temp_cal->temp_sensitivity[0] = OTC_INITIAL_SENSITIVITY;
158   over_temp_cal->temp_sensitivity[1] = OTC_INITIAL_SENSITIVITY;
159   over_temp_cal->temp_sensitivity[2] = OTC_INITIAL_SENSITIVITY;
160   memset(over_temp_cal->sensor_intercept, 0, 3 * sizeof(float));
161 }
162 
163 // Checks that the input temperature value is within the valid range. If outside
164 // of range, then 'temperature_celsius' is coerced to within the limits.
checkAndEnforceTemperatureRange(float * temperature_celsius)165 static bool checkAndEnforceTemperatureRange(float *temperature_celsius) {
166   if (*temperature_celsius > OVERTEMPCAL_TEMP_MAX_CELSIUS) {
167     *temperature_celsius = OVERTEMPCAL_TEMP_MAX_CELSIUS;
168     return false;
169   }
170   if (*temperature_celsius < OVERTEMPCAL_TEMP_MIN_CELSIUS) {
171     *temperature_celsius = OVERTEMPCAL_TEMP_MIN_CELSIUS;
172     return false;
173   }
174   return true;
175 }
176 
177 // Returns "true" if the candidate linear model parameters are within the valid
178 // range, and not all zeros.
isValidOtcLinearModel(const struct OverTempCal * over_temp_cal,float temp_sensitivity,float sensor_intercept)179 static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
180                    float temp_sensitivity, float sensor_intercept) {
181   ASSERT_NOT_NULL(over_temp_cal);
182 
183   return NANO_ABS(temp_sensitivity) < over_temp_cal->temp_sensitivity_limit &&
184          NANO_ABS(sensor_intercept) < over_temp_cal->sensor_intercept_limit &&
185          NANO_ABS(temp_sensitivity) > OTC_MODELDATA_NEAR_ZERO_TOL &&
186          NANO_ABS(sensor_intercept) > OTC_MODELDATA_NEAR_ZERO_TOL;
187 }
188 
189 // Returns "true" if 'offset' and 'offset_temp_celsius' is valid.
isValidOtcOffset(const float * offset,float offset_temp_celsius)190 static bool isValidOtcOffset(const float *offset, float offset_temp_celsius) {
191   ASSERT_NOT_NULL(offset);
192 
193   // Simple check to ensure that:
194   //   1. All of the input data is non "zero".
195   //   2. The offset temperature is within the valid range.
196   if (NANO_ABS(offset[0]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
197       NANO_ABS(offset[1]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
198       NANO_ABS(offset[2]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
199       NANO_ABS(offset_temp_celsius) < OTC_MODELDATA_NEAR_ZERO_TOL) {
200     return false;
201   }
202 
203   // Only returns the "check" result. Don't care about coercion.
204   return checkAndEnforceTemperatureRange(&offset_temp_celsius);
205 }
206 
207 #ifdef OVERTEMPCAL_DBG_ENABLED
208 // This helper function stores all of the debug tracking information necessary
209 // for printing log messages.
210 static void updateDebugData(struct OverTempCal* over_temp_cal);
211 #endif  // OVERTEMPCAL_DBG_ENABLED
212 
213 /////// FUNCTION DEFINITIONS //////////////////////////////////////////////////
214 
overTempCalInit(struct OverTempCal * over_temp_cal,size_t min_num_model_pts,uint64_t min_update_interval_nanos,float delta_temp_per_bin,float max_error_limit,uint64_t age_limit_nanos,float temp_sensitivity_limit,float sensor_intercept_limit,bool over_temp_enable)215 void overTempCalInit(struct OverTempCal *over_temp_cal,
216                      size_t min_num_model_pts,
217                      uint64_t min_update_interval_nanos,
218                      float delta_temp_per_bin, float max_error_limit,
219                      uint64_t age_limit_nanos, float temp_sensitivity_limit,
220                      float sensor_intercept_limit, bool over_temp_enable) {
221   ASSERT_NOT_NULL(over_temp_cal);
222 
223   // Clears OverTempCal memory.
224   memset(over_temp_cal, 0, sizeof(struct OverTempCal));
225 
226   // Initializes the pointer to the most recent sensor offset estimate. Sets it
227   // as the first element in 'model_data'.
228   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
229 
230   // Initializes the OTC linear model parameters.
231   resetOtcLinearModel(over_temp_cal);
232 
233   // Initializes the model identification parameters.
234   over_temp_cal->new_overtemp_model_available = false;
235   over_temp_cal->min_num_model_pts = min_num_model_pts;
236   over_temp_cal->min_update_interval_nanos = min_update_interval_nanos;
237   over_temp_cal->delta_temp_per_bin = delta_temp_per_bin;
238   over_temp_cal->max_error_limit = max_error_limit;
239   over_temp_cal->age_limit_nanos = age_limit_nanos;
240   over_temp_cal->temp_sensitivity_limit = temp_sensitivity_limit;
241   over_temp_cal->sensor_intercept_limit = sensor_intercept_limit;
242   over_temp_cal->over_temp_enable = over_temp_enable;
243 
244   // Initialize the sensor's temperature with a good initial operating point.
245   over_temp_cal->temperature_celsius = JUMPSTART_START_TEMP_CELSIUS;
246 
247 #ifdef OVERTEMPCAL_DBG_ENABLED
248   CAL_DEBUG_LOG("[OVER_TEMP_CAL:MEMORY]", "sizeof(struct OverTempCal): %lu",
249                 (unsigned long int)sizeof(struct OverTempCal));
250 
251   if (over_temp_cal->over_temp_enable) {
252     CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]",
253                   "Over-temperature compensation ENABLED.");
254   } else {
255     CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]",
256                   "Over-temperature compensation DISABLED.");
257   }
258 #endif  // OVERTEMPCAL_DBG_ENABLED
259 }
260 
overTempCalSetModel(struct OverTempCal * over_temp_cal,const float * offset,float offset_temp_celsius,uint64_t timestamp_nanos,const float * temp_sensitivity,const float * sensor_intercept,bool jump_start_model)261 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
262                          float offset_temp_celsius, uint64_t timestamp_nanos,
263                          const float *temp_sensitivity,
264                          const float *sensor_intercept, bool jump_start_model) {
265   ASSERT_NOT_NULL(over_temp_cal);
266   ASSERT_NOT_NULL(offset);
267   ASSERT_NOT_NULL(temp_sensitivity);
268   ASSERT_NOT_NULL(sensor_intercept);
269 
270   // Initializes the OTC linear model parameters.
271   resetOtcLinearModel(over_temp_cal);
272 
273   // Sets the model parameters if they are within the acceptable limits.
274   // Includes a check to reject input model parameters that may have been passed
275   // in as all zeros.
276   size_t i;
277   for (i = 0; i < 3; i++) {
278     if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
279                               sensor_intercept[i])) {
280       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
281       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
282     }
283   }
284 
285   // Sets the model update time to the current timestamp.
286   over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
287 
288   // Model "Jump-Start".
289   const bool model_jump_started =
290       (jump_start_model) ? jumpStartModelData(over_temp_cal) : false;
291 
292   if (!model_jump_started) {
293     // Checks that the new offset data is valid.
294     if (isValidOtcOffset(offset, offset_temp_celsius)) {
295       // Sets the initial over-temp calibration estimate and model data.
296       over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
297       setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
298                          timestamp_nanos);
299       over_temp_cal->num_model_pts = 1;
300     } else {
301       // No valid offset data to load.
302       over_temp_cal->num_model_pts = 0;
303 #ifdef OVERTEMPCAL_DBG_ENABLED
304       CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
305                     "No valid sensor offset vector to load.");
306 #endif  // OVERTEMPCAL_DBG_ENABLED
307     }
308   } else {
309     // Finds the offset nearest the sensor's current temperature.
310     findNearestEstimate(over_temp_cal);
311   }
312 
313   // Updates the 'compensated_offset_previous' vector to prevent from
314   // immediately triggering a new calibration update.
315   float compensated_offset_temperature_celsius = 0.0f;
316   getCalOffset(over_temp_cal, timestamp_nanos,
317                &compensated_offset_temperature_celsius,
318                over_temp_cal->compensated_offset_previous);
319 
320 #ifdef OVERTEMPCAL_DBG_ENABLED
321   // Prints the updated model data.
322   CAL_DEBUG_LOG(
323       "[OVER_TEMP_CAL:RECALL]",
324       "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
325       "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
326       "%s%d.%06d, "
327       "%s%d.%06d, %s%d.%06d",
328       CAL_ENCODE_FLOAT(offset_temp_celsius, 6),
329       CAL_ENCODE_FLOAT(offset[0], 6),
330       CAL_ENCODE_FLOAT(offset[1], 6),
331       CAL_ENCODE_FLOAT(offset[2], 6),
332       CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
333       CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
334       CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
335       CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
336       CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
337       CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
338 
339   // Resets the debug print machine to ensure that updateDebugData() can
340   // produce a debug report and interupt any ongoing report.
341   over_temp_cal->debug_state = OTC_IDLE;
342 
343   // Triggers a debug print out to view the new model parameters.
344   updateDebugData(over_temp_cal);
345 #endif  // OVERTEMPCAL_DBG_ENABLED
346 }
347 
overTempCalGetModel(struct OverTempCal * over_temp_cal,float * offset,float * offset_temp_celsius,uint64_t * timestamp_nanos,float * temp_sensitivity,float * sensor_intercept)348 void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset,
349                          float *offset_temp_celsius, uint64_t *timestamp_nanos,
350                          float *temp_sensitivity, float *sensor_intercept) {
351   ASSERT_NOT_NULL(over_temp_cal);
352   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
353   ASSERT_NOT_NULL(offset);
354   ASSERT_NOT_NULL(offset_temp_celsius);
355   ASSERT_NOT_NULL(timestamp_nanos);
356   ASSERT_NOT_NULL(temp_sensitivity);
357   ASSERT_NOT_NULL(sensor_intercept);
358 
359   // Gets the latest over-temp calibration model data.
360   memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity, 3 * sizeof(float));
361   memcpy(sensor_intercept, over_temp_cal->sensor_intercept, 3 * sizeof(float));
362   *timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
363 
364   // Gets the latest temperature compensated offset estimate.
365   getCalOffset(over_temp_cal, *timestamp_nanos, offset_temp_celsius, offset);
366 
367 #ifdef OVERTEMPCAL_DBG_ENABLED
368   // Prints the updated model data.
369   CAL_DEBUG_LOG(
370       "[OVER_TEMP_CAL:STORED]",
371       "Temperature|Offset|Sensitivity|Intercept [rps]: %s%d.%06d, | %s%d.%06d, "
372       "%s%d.%06d, %s%d.%06d | %s%d.%06d, %s%d.%06d, %s%d.%06d | "
373       "%s%d.%06d, "
374       "%s%d.%06d, %s%d.%06d",
375       CAL_ENCODE_FLOAT(*offset_temp_celsius, 6),
376       CAL_ENCODE_FLOAT(offset[0], 6),
377       CAL_ENCODE_FLOAT(offset[1], 6),
378       CAL_ENCODE_FLOAT(offset[2], 6),
379       CAL_ENCODE_FLOAT(temp_sensitivity[0], 6),
380       CAL_ENCODE_FLOAT(temp_sensitivity[1], 6),
381       CAL_ENCODE_FLOAT(temp_sensitivity[2], 6),
382       CAL_ENCODE_FLOAT(sensor_intercept[0], 6),
383       CAL_ENCODE_FLOAT(sensor_intercept[1], 6),
384       CAL_ENCODE_FLOAT(sensor_intercept[2], 6));
385 #endif  // OVERTEMPCAL_DBG_ENABLED
386 }
387 
overTempCalSetModelData(struct OverTempCal * over_temp_cal,size_t data_length,const struct OverTempCalDataPt * model_data)388 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
389                              size_t data_length,
390                              const struct OverTempCalDataPt *model_data) {
391   ASSERT_NOT_NULL(over_temp_cal);
392   ASSERT_NOT_NULL(model_data);
393 
394   // Load only "good" data from the input 'model_data'.
395   over_temp_cal->num_model_pts = NANO_MIN(data_length, OVERTEMPCAL_MODEL_SIZE);
396   size_t i;
397   size_t valid_data_count = 0;
398   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
399     if (isValidOtcOffset(model_data[i].offset,
400                          model_data[i].offset_temp_celsius)) {
401       memcpy(&over_temp_cal->model_data[i], &model_data[i],
402              sizeof(struct OverTempCalDataPt));
403       valid_data_count++;
404     }
405   }
406   over_temp_cal->num_model_pts = valid_data_count;
407 
408   // Initializes the OTC linear model parameters.
409   resetOtcLinearModel(over_temp_cal);
410 
411   // Finds the offset nearest the sensor's current temperature.
412   findNearestEstimate(over_temp_cal);
413 
414   // Updates the 'compensated_offset_previous' vector to prevent from
415   // immediately triggering a new calibration update.
416   float compensated_offset_temperature_celsius = 0.0f;
417   getCalOffset(over_temp_cal, /*timestamp_nanos=*/0,
418                &compensated_offset_temperature_celsius,
419                over_temp_cal->compensated_offset_previous);
420 
421 #ifdef OVERTEMPCAL_DBG_ENABLED
422   // Prints the updated model data.
423   CAL_DEBUG_LOG("[OVER_TEMP_CAL:RECALL]",
424                 "Over-temperature full model data set recalled.");
425   // Resets the debug print machine to ensure that computeModelUpdate() can
426   // produce a debug report and interupt any ongoing report.
427   over_temp_cal->debug_state = OTC_IDLE;
428 #endif  // OVERTEMPCAL_DBG_ENABLED
429 
430   // Ensures that minimum number of points required for a model fit has been
431   // satisfied and recomputes the OTC model parameters.
432   if (over_temp_cal->num_model_pts > over_temp_cal->min_num_model_pts) {
433     // Computes and replaces the model parameters. If successful, this will
434     // trigger a "new calibration" update.
435     computeModelUpdate(over_temp_cal,
436                        over_temp_cal->modelupdate_timestamp_nanos);
437   }
438 }
439 
overTempCalGetModelData(struct OverTempCal * over_temp_cal,size_t * data_length,struct OverTempCalDataPt * model_data)440 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
441                              size_t *data_length,
442                              struct OverTempCalDataPt *model_data) {
443   ASSERT_NOT_NULL(over_temp_cal);
444   *data_length = over_temp_cal->num_model_pts;
445   memcpy(model_data, over_temp_cal->model_data,
446          over_temp_cal->num_model_pts * sizeof(struct OverTempCalDataPt));
447 }
448 
overTempCalGetOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float * compensated_offset_temperature_celsius,float * compensated_offset)449 bool overTempCalGetOffset(struct OverTempCal *over_temp_cal,
450                           uint64_t timestamp_nanos,
451                           float *compensated_offset_temperature_celsius,
452                           float *compensated_offset) {
453   // Gets the temperature compensated sensor offset estimate.
454   getCalOffset(over_temp_cal, timestamp_nanos,
455                compensated_offset_temperature_celsius, compensated_offset);
456 
457   // If the compensated_offset value has changed significantly then return
458   // 'true' status.
459   bool offset_has_changed = false;
460   int i;
461   for (i = 0; i < 3; i++) {
462     if (NANO_ABS(over_temp_cal->compensated_offset_previous[i] -
463                  compensated_offset[i]) >= SIGNIFICANT_OFFSET_CHANGE_RPS) {
464       offset_has_changed = true;
465 
466       // Update the 'compensated_offset_previous' vector.
467       memcpy(over_temp_cal->compensated_offset_previous, compensated_offset,
468              3 * sizeof(float));
469       break;
470     }
471   }
472 
473   return offset_has_changed;
474 }
475 
overTempCalRemoveOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float xi,float yi,float zi,float * xo,float * yo,float * zo)476 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
477                              uint64_t timestamp_nanos, float xi, float yi,
478                              float zi, float *xo, float *yo, float *zo) {
479   ASSERT_NOT_NULL(over_temp_cal);
480   ASSERT_NOT_NULL(xo);
481   ASSERT_NOT_NULL(yo);
482   ASSERT_NOT_NULL(zo);
483 
484   // Determines whether over-temp compensation will be applied.
485   if (over_temp_cal->over_temp_enable) {
486     // Gets the temperature compensated sensor offset estimate.
487     float compensated_offset[3] = {0.0f, 0.0f, 0.0f};
488     float compensated_offset_temperature_celsius = 0.0f;
489     getCalOffset(over_temp_cal, timestamp_nanos,
490                  &compensated_offset_temperature_celsius, compensated_offset);
491 
492     // Removes the over-temperature compensated offset from the input sensor
493     // data.
494     *xo = xi - compensated_offset[0];
495     *yo = yi - compensated_offset[1];
496     *zo = zi - compensated_offset[2];
497   } else {
498     *xo = xi;
499     *yo = yi;
500     *zo = zi;
501   }
502 }
503 
overTempCalNewModelUpdateAvailable(struct OverTempCal * over_temp_cal)504 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) {
505   ASSERT_NOT_NULL(over_temp_cal);
506   const bool update_available = over_temp_cal->new_overtemp_model_available &&
507                                 over_temp_cal->over_temp_enable;
508 
509   // The 'new_overtemp_model_available' flag is reset when it is read here.
510   over_temp_cal->new_overtemp_model_available = false;
511 
512   return update_available;
513 }
514 
overTempCalUpdateSensorEstimate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,const float * offset,float temperature_celsius)515 void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal,
516                                      uint64_t timestamp_nanos,
517                                      const float *offset,
518                                      float temperature_celsius) {
519   ASSERT_NOT_NULL(over_temp_cal);
520   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
521   ASSERT_NOT_NULL(offset);
522   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
523 
524   // Checks that the new offset data is valid, returns if bad.
525   if (!isValidOtcOffset(offset, temperature_celsius)) {
526     return;
527   }
528 
529   // Prevent a divide by zero below.
530   if (over_temp_cal->delta_temp_per_bin <= 0) {
531     return;
532   }
533 
534   // Checks whether this offset estimate is a likely outlier. A limit is placed
535   // on 'num_outliers', the previous number of successive rejects, to prevent
536   // too many back-to-back rejections.
537   if (over_temp_cal->num_outliers < OVERTEMPCAL_MAX_OUTLIER_COUNT) {
538     if (outlierCheck(over_temp_cal, offset, 0, temperature_celsius) ||
539         outlierCheck(over_temp_cal, offset, 1, temperature_celsius) ||
540         outlierCheck(over_temp_cal, offset, 2, temperature_celsius)) {
541       // Increments the count of rejected outliers.
542       over_temp_cal->num_outliers++;
543 
544 #ifdef OVERTEMPCAL_DBG_ENABLED
545       CAL_DEBUG_LOG("[OVER_TEMP_CAL:OUTLIER]",
546                     "Offset|Temperature|Time [mdps|Celcius|nsec] = "
547                     "%s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%03d, %llu",
548                     CAL_ENCODE_FLOAT(offset[0] * RAD_TO_MILLI_DEGREES, 6),
549                     CAL_ENCODE_FLOAT(offset[1] * RAD_TO_MILLI_DEGREES, 6),
550                     CAL_ENCODE_FLOAT(offset[2] * RAD_TO_MILLI_DEGREES, 6),
551                     CAL_ENCODE_FLOAT(temperature_celsius, 3),
552                     (unsigned long long int)timestamp_nanos);
553 #endif  // OVERTEMPCAL_DBG_ENABLED
554 
555       return;  // Outlier detected: skips adding this offset to the model.
556     } else {
557       // Resets the count of rejected outliers.
558       over_temp_cal->num_outliers = 0;
559     }
560   } else {
561     // Resets the count of rejected outliers.
562     over_temp_cal->num_outliers = 0;
563   }
564 
565   // Computes the temperature bin range data.
566   const int32_t bin_num =
567       CAL_FLOOR(temperature_celsius / over_temp_cal->delta_temp_per_bin);
568   const float temp_lo_check = bin_num * over_temp_cal->delta_temp_per_bin;
569   const float temp_hi_check = (bin_num + 1) * over_temp_cal->delta_temp_per_bin;
570 
571   // The rules for accepting new offset estimates into the 'model_data'
572   // collection:
573   //    1) The temperature domain is divided into bins each spanning
574   //       'delta_temp_per_bin'.
575   //    2) Find and replace the i'th 'model_data' estimate data if:
576   //          Let, bin_num = floor(temperature_celsius / delta_temp_per_bin)
577   //          temp_lo_check = bin_num * delta_temp_per_bin
578   //          temp_hi_check = (bin_num + 1) * delta_temp_per_bin
579   //          Check condition:
580   //          temp_lo_check <= model_data[i].offset_temp_celsius < temp_hi_check
581   bool replaced_one = false;
582   size_t i = 0;
583   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
584     if (over_temp_cal->model_data[i].offset_temp_celsius < temp_hi_check &&
585         over_temp_cal->model_data[i].offset_temp_celsius >= temp_lo_check) {
586       // NOTE - the pointer to the new model data point is set here; the offset
587       // data is set below in the call to 'setNearestEstimate'.
588       over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
589       replaced_one = true;
590       break;
591     }
592   }
593 
594   // NOTE - the pointer to the new model data point is set here; the offset
595   // data is set below in the call to 'setNearestEstimate'.
596   if (!replaced_one && over_temp_cal->num_model_pts < OVERTEMPCAL_MODEL_SIZE) {
597     if (over_temp_cal->num_model_pts < OVERTEMPCAL_MODEL_SIZE) {
598       // 3) If nothing was replaced, and the 'model_data' buffer is not full
599       //    then add the estimate data to the array.
600       over_temp_cal->nearest_offset =
601           &over_temp_cal->model_data[over_temp_cal->num_model_pts];
602       over_temp_cal->num_model_pts++;
603     } else {
604       // 4) Otherwise (nothing was replaced and buffer is full), replace the
605       //    oldest data with the incoming one.
606       over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
607       for (i = 1; i < over_temp_cal->num_model_pts; i++) {
608         if (over_temp_cal->nearest_offset->timestamp_nanos <
609             over_temp_cal->model_data[i].timestamp_nanos) {
610           over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
611         }
612       }
613     }
614   }
615 
616   // Updates the model estimate data nearest to the sensor's temperature.
617   setNearestEstimate(over_temp_cal, offset, temperature_celsius,
618                      timestamp_nanos);
619 
620 #ifdef OVERTEMPCAL_DBG_ENABLED
621   // Updates the total number of received sensor offset estimates.
622   over_temp_cal->debug_num_estimates++;
623 #endif  // OVERTEMPCAL_DBG_ENABLED
624 
625   // The rules for determining whether a new model fit is computed are:
626   //    1) A minimum number of data points must have been collected:
627   //          num_model_pts >= min_num_model_pts
628   //       NOTE: Collecting 'num_model_pts' and given that only one point is
629   //       kept per temperature bin (spanning a thermal range specified by
630   //       'delta_temp_per_bin'), implies that model data covers at least,
631   //          model_temperature_span >= 'num_model_pts' * delta_temp_per_bin
632   //    2) New model updates will not occur for intervals less than:
633   //          (current_timestamp_nanos - modelupdate_timestamp_nanos) <
634   //            min_update_interval_nanos
635   if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts ||
636       timestamp_nanos < over_temp_cal->min_update_interval_nanos +
637                             over_temp_cal->modelupdate_timestamp_nanos) {
638 #ifdef OVERTEMPCAL_DBG_ENABLED
639     // Triggers a log printout to show the updated sensor offset estimate.
640     updateDebugData(over_temp_cal);
641 #endif  // OVERTEMPCAL_DBG_ENABLED
642   } else {
643     // The conditions satisfy performing a new model update.
644     computeModelUpdate(over_temp_cal, timestamp_nanos);
645   }
646 }
647 
overTempCalSetTemperature(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius)648 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
649                                uint64_t timestamp_nanos,
650                                float temperature_celsius) {
651   ASSERT_NOT_NULL(over_temp_cal);
652 
653 #ifdef OVERTEMPCAL_DBG_ENABLED
654 #ifdef OVERTEMPCAL_DBG_LOG_TEMP
655   static uint64_t wait_timer = 0;
656   // Prints the sensor temperature trajectory for debugging purposes.
657   // This throttles the print statements.
658   if (timestamp_nanos >= 1000000000 + wait_timer) {
659     wait_timer = timestamp_nanos;  // Starts the wait timer.
660 
661     // Prints out temperature and the current timestamp.
662     CAL_DEBUG_LOG("[OVER_TEMP_CAL:TEMP]",
663                   "Temperature|Time [C|nsec] = %s%d.%06d, %llu",
664                   CAL_ENCODE_FLOAT(temperature_celsius, 6),
665                   (unsigned long long int)timestamp_nanos);
666   }
667 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
668 #endif  // OVERTEMPCAL_DBG_ENABLED
669 
670   // Checks that the offset temperature is within a valid range, saturates if
671   // outside.
672   checkAndEnforceTemperatureRange(&temperature_celsius);
673 
674   // Updates the sensor temperature.
675   over_temp_cal->temperature_celsius = temperature_celsius;
676 
677   // Searches for the sensor offset estimate closest to the current
678   // temperature. A timer is used to limit the rate at which this search is
679   // performed.
680   if (over_temp_cal->num_model_pts > 0 &&
681       timestamp_nanos >=
682           OVERTEMPCAL_NEAREST_NANOS + over_temp_cal->nearest_search_timer) {
683     findNearestEstimate(over_temp_cal);
684     over_temp_cal->nearest_search_timer = timestamp_nanos;  // Reset timer.
685   }
686 }
687 
overTempGetModelError(const struct OverTempCal * over_temp_cal,const float * temp_sensitivity,const float * sensor_intercept,float * max_error)688 void overTempGetModelError(const struct OverTempCal *over_temp_cal,
689                    const float *temp_sensitivity, const float *sensor_intercept,
690                    float *max_error) {
691   ASSERT_NOT_NULL(over_temp_cal);
692   ASSERT_NOT_NULL(temp_sensitivity);
693   ASSERT_NOT_NULL(sensor_intercept);
694   ASSERT_NOT_NULL(max_error);
695 
696   size_t i;
697   size_t j;
698   float max_error_test;
699   memset(max_error, 0, 3 * sizeof(float));
700 
701   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
702     for (j = 0; j < 3; j++) {
703       max_error_test =
704           NANO_ABS(over_temp_cal->model_data[i].offset[j] -
705                    (temp_sensitivity[j] *
706                         over_temp_cal->model_data[i].offset_temp_celsius +
707                     sensor_intercept[j]));
708       if (max_error_test > max_error[j]) {
709         max_error[j] = max_error_test;
710       }
711     }
712   }
713 }
714 
715 /////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
716 
getCalOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float * compensated_offset_temperature_celsius,float * compensated_offset)717 void getCalOffset(struct OverTempCal *over_temp_cal, uint64_t timestamp_nanos,
718                   float *compensated_offset_temperature_celsius,
719                   float *compensated_offset) {
720   ASSERT_NOT_NULL(over_temp_cal);
721   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
722   ASSERT_NOT_NULL(compensated_offset);
723   ASSERT_NOT_NULL(compensated_offset_temperature_celsius);
724 
725   // Sets the sensor temperature associated with the compensated offset.
726   *compensated_offset_temperature_celsius = over_temp_cal->temperature_celsius;
727 
728   // Removes very old data from the collected model estimates (eliminates
729   // drift-compromised data). Only does this when there is more than one
730   // estimate in the model (i.e., don't want to remove all data, even if it is
731   // very old [something is likely better than nothing]).
732   if ((timestamp_nanos >=
733        OVERTEMPCAL_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer) &&
734       over_temp_cal->num_model_pts > 1) {
735     over_temp_cal->stale_data_timer = timestamp_nanos;  // Resets timer.
736 
737     if (removeStaleModelData(over_temp_cal, timestamp_nanos)) {
738       // If anything was removed, then this attempts to recompute the model.
739       if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) {
740         computeModelUpdate(over_temp_cal, timestamp_nanos);
741       }
742     }
743   }
744 
745   size_t index;
746   for (index = 0; index < 3; index++) {
747     if (over_temp_cal->temp_sensitivity[index] >= OTC_INITIAL_SENSITIVITY ||
748         NANO_ABS(over_temp_cal->temperature_celsius -
749                  over_temp_cal->nearest_offset->offset_temp_celsius) <
750             over_temp_cal->delta_temp_per_bin) {
751       // Use the nearest estimate to perform the compensation if either of the
752       // following is true:
753       //    1) This axis model is in its initial state.
754       //    2) The sensor's temperature is within a small neighborhood of the
755       //       'nearest_offset'.
756       // compensated_offset = nearest_offset
757       //
758       // If either of the above conditions applies and 'nearest_offset' is not
759       // defined, then the offset returned is zero.
760       compensated_offset[index] =
761           (over_temp_cal->num_model_pts > 0)
762               ? over_temp_cal->nearest_offset->offset[index]
763               : 0.0f;
764     } else {
765       // Offset computed from the linear model:
766       //  compensated_offset = (temp_sensitivity * temperature +
767       //  sensor_intercept)
768       compensated_offset[index] = (over_temp_cal->temp_sensitivity[index] *
769                                        over_temp_cal->temperature_celsius +
770                                    over_temp_cal->sensor_intercept[index]);
771     }
772   }
773 }
774 
setNearestEstimate(struct OverTempCal * over_temp_cal,const float * offset,float offset_temp_celsius,uint64_t timestamp_nanos)775 void setNearestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
776                        float offset_temp_celsius, uint64_t timestamp_nanos) {
777   ASSERT_NOT_NULL(over_temp_cal);
778   ASSERT_NOT_NULL(offset);
779   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
780 
781   // Sets the latest over-temp calibration estimate.
782   memcpy(over_temp_cal->nearest_offset->offset, offset, 3 * sizeof(float));
783   over_temp_cal->nearest_offset->offset_temp_celsius = offset_temp_celsius;
784   over_temp_cal->nearest_offset->timestamp_nanos = timestamp_nanos;
785 }
786 
computeModelUpdate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)787 void computeModelUpdate(struct OverTempCal *over_temp_cal,
788                         uint64_t timestamp_nanos) {
789   ASSERT_NOT_NULL(over_temp_cal);
790 
791   // Updates the linear model fit.
792   float temp_sensitivity[3];
793   float sensor_intercept[3];
794   updateModel(over_temp_cal, temp_sensitivity, sensor_intercept);
795 
796   // Computes the maximum error over all of the model data.
797   float max_error[3];
798   overTempGetModelError(over_temp_cal, temp_sensitivity, sensor_intercept,
799                         max_error);
800 
801   //    3) A new set of model parameters are accepted if:
802   //         i.  The model fit error is less than, 'max_error_limit'. See
803   //             overTempGetModelError() for error metric description.
804   //         ii. The model fit parameters must be within certain absolute
805   //             bounds:
806   //               a. NANO_ABS(temp_sensitivity) < temp_sensitivity_limit
807   //               b. NANO_ABS(sensor_intercept) < sensor_intercept_limit
808   size_t i;
809   bool updated_one = false;
810   for (i = 0; i < 3; i++) {
811     if (max_error[i] < over_temp_cal->max_error_limit &&
812         isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
813                               sensor_intercept[i])) {
814       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
815       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
816       updated_one = true;
817     } else {
818 #ifdef OVERTEMPCAL_DBG_ENABLED
819       CAL_DEBUG_LOG(
820           "[OVER_TEMP_CAL:REJECT]",
821           "%c-Axis Parameters|Max Error|Time [mdps/C|mdps|mdps|nsec] = "
822           "%s%d.%06d, %s%d.%06d, %s%d.%06d, %llu",
823           kDebugAxisLabel[i],
824           CAL_ENCODE_FLOAT(temp_sensitivity[i] * RAD_TO_MILLI_DEGREES, 6),
825           CAL_ENCODE_FLOAT(sensor_intercept[i] * RAD_TO_MILLI_DEGREES, 6),
826           CAL_ENCODE_FLOAT(max_error[i] * RAD_TO_MILLI_DEGREES, 6),
827           (unsigned long long int)timestamp_nanos);
828 #endif  // OVERTEMPCAL_DBG_ENABLED
829     }
830   }
831 
832   // If at least one of the axes updated then consider this a valid model
833   // update.
834   if (updated_one) {
835     // Resets the timer and sets the update flag.
836     over_temp_cal->modelupdate_timestamp_nanos = timestamp_nanos;
837     over_temp_cal->new_overtemp_model_available = true;
838 
839 #ifdef OVERTEMPCAL_DBG_ENABLED
840     // Updates the total number of model updates, the debug data package, and
841     // triggers a log printout.
842     over_temp_cal->debug_num_model_updates++;
843     updateDebugData(over_temp_cal);
844 #endif  // OVERTEMPCAL_DBG_ENABLED
845   }
846 }
847 
findNearestEstimate(struct OverTempCal * over_temp_cal)848 void findNearestEstimate(struct OverTempCal *over_temp_cal) {
849   ASSERT_NOT_NULL(over_temp_cal);
850 
851   // Performs a brute force search for the estimate nearest the current sensor
852   // temperature.
853   size_t i = 0;
854   float dtemp_new = 0.0f;
855   float dtemp_old = FLT_MAX;
856   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
857   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
858     dtemp_new = NANO_ABS(over_temp_cal->model_data[i].offset_temp_celsius -
859                     over_temp_cal->temperature_celsius);
860     if (dtemp_new < dtemp_old) {
861       over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
862       dtemp_old = dtemp_new;
863     }
864   }
865 }
866 
removeStaleModelData(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)867 bool removeStaleModelData(struct OverTempCal *over_temp_cal,
868                           uint64_t timestamp_nanos) {
869   ASSERT_NOT_NULL(over_temp_cal);
870 
871   size_t i;
872   bool removed_one = false;
873   for (i = 0; i < over_temp_cal->num_model_pts; i++) {
874     if (timestamp_nanos > over_temp_cal->model_data[i].timestamp_nanos &&
875         timestamp_nanos > over_temp_cal->age_limit_nanos +
876                               over_temp_cal->model_data[i].timestamp_nanos) {
877       removed_one |= removeModelDataByIndex(over_temp_cal, i);
878     }
879   }
880 
881   // Updates the latest offset so that it is the one nearest to the current
882   // temperature.
883   findNearestEstimate(over_temp_cal);
884 
885   return removed_one;
886 }
887 
removeModelDataByIndex(struct OverTempCal * over_temp_cal,size_t model_index)888 bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
889                             size_t model_index) {
890   ASSERT_NOT_NULL(over_temp_cal);
891 
892   // This function will not remove all of the model data. At least one model
893   // sample will be left.
894   if (over_temp_cal->num_model_pts <= 1) {
895     return false;
896   }
897 
898 #ifdef OVERTEMPCAL_DBG_ENABLED
899   CAL_DEBUG_LOG(
900       "[OVER_TEMP_CAL:REMOVE]",
901       "Offset|Temp|Time [mdps|C|nsec] = %s%d.%06d, %s%d.%06d, %s%d.%06d, "
902       "%s%d.%03d, %llu",
903       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[0] *
904                            RAD_TO_MILLI_DEGREES,
905                        6),
906       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
907                            RAD_TO_MILLI_DEGREES,
908                        6),
909       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
910                            RAD_TO_MILLI_DEGREES,
911                        6),
912       CAL_ENCODE_FLOAT(
913           over_temp_cal->model_data[model_index].offset_temp_celsius, 3),
914       (unsigned long long int)over_temp_cal->model_data[model_index]
915           .timestamp_nanos);
916 #endif  // OVERTEMPCAL_DBG_ENABLED
917 
918   // Remove the model data at 'model_index'.
919   size_t i;
920   for (i = model_index; i < over_temp_cal->num_model_pts - 1; i++) {
921     memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1],
922            sizeof(struct OverTempCalDataPt));
923   }
924   over_temp_cal->num_model_pts--;
925 
926   return true;
927 }
928 
jumpStartModelData(struct OverTempCal * over_temp_cal)929 bool jumpStartModelData(struct OverTempCal *over_temp_cal) {
930   ASSERT_NOT_NULL(over_temp_cal);
931   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
932 
933   // Prevent a divide by zero below.
934   if (over_temp_cal->delta_temp_per_bin <= 0) {
935     return false;
936   }
937 
938   // In normal operation the offset estimates enter into the 'model_data' array
939   // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart
940   // data produced here requires that the model parameters have all been fully
941   // defined and are all within the valid range.
942   size_t i;
943   for (i = 0; i < 3; i++) {
944     if (!isValidOtcLinearModel(over_temp_cal,
945                                over_temp_cal->temp_sensitivity[i],
946                                over_temp_cal->sensor_intercept[i])) {
947       return false;
948     }
949   }
950 
951   // Any pre-existing model data points will be overwritten.
952   over_temp_cal->num_model_pts = 0;
953 
954   // This defines the minimum contiguous set of points to allow a model update
955   // when the next offset estimate is received. They are placed at a common
956   // temperature range that is likely to get replaced with actual data soon.
957   const int32_t start_bin_num = CAL_FLOOR(JUMPSTART_START_TEMP_CELSIUS /
958                                           over_temp_cal->delta_temp_per_bin);
959   float offset_temp_celsius =
960       (start_bin_num + 0.5f) * over_temp_cal->delta_temp_per_bin;
961 
962   size_t j;
963   for (i = 0; i < over_temp_cal->min_num_model_pts; i++) {
964     float offset[3];
965     const uint64_t timestamp_nanos = over_temp_cal->modelupdate_timestamp_nanos;
966     for (j = 0; j < 3; j++) {
967       offset[j] = over_temp_cal->temp_sensitivity[j] * offset_temp_celsius +
968                   over_temp_cal->sensor_intercept[j];
969     }
970     over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
971     setNearestEstimate(over_temp_cal, offset, offset_temp_celsius,
972                       timestamp_nanos);
973     offset_temp_celsius += over_temp_cal->delta_temp_per_bin;
974     over_temp_cal->num_model_pts++;
975   }
976 
977 #ifdef OVERTEMPCAL_DBG_ENABLED
978   if (over_temp_cal->num_model_pts > 0) {
979     CAL_DEBUG_LOG("[OVER_TEMP_CAL:INIT]", "Model Jump-Start:  #Points = %lu.",
980                   (unsigned long int)over_temp_cal->num_model_pts);
981   }
982 #endif  // OVERTEMPCAL_DBG_ENABLED
983 
984   return (over_temp_cal->num_model_pts > 0);
985 }
986 
updateModel(const struct OverTempCal * over_temp_cal,float * temp_sensitivity,float * sensor_intercept)987 void updateModel(const struct OverTempCal *over_temp_cal,
988                  float *temp_sensitivity, float *sensor_intercept) {
989   ASSERT_NOT_NULL(over_temp_cal);
990   ASSERT_NOT_NULL(temp_sensitivity);
991   ASSERT_NOT_NULL(sensor_intercept);
992   ASSERT(over_temp_cal->num_model_pts > 0);
993 
994   float st = 0.0f, stt = 0.0f;
995   float sx = 0.0f, stsx = 0.0f;
996   float sy = 0.0f, stsy = 0.0f;
997   float sz = 0.0f, stsz = 0.0f;
998   const size_t n = over_temp_cal->num_model_pts;
999   size_t i = 0;
1000 
1001   // First pass computes the mean values.
1002   for (i = 0; i < n; ++i) {
1003     st += over_temp_cal->model_data[i].offset_temp_celsius;
1004     sx += over_temp_cal->model_data[i].offset[0];
1005     sy += over_temp_cal->model_data[i].offset[1];
1006     sz += over_temp_cal->model_data[i].offset[2];
1007   }
1008 
1009   // Second pass computes the mean corrected second moment values.
1010   const float inv_n = 1.0f / n;
1011   for (i = 0; i < n; ++i) {
1012     const float t =
1013         over_temp_cal->model_data[i].offset_temp_celsius - st * inv_n;
1014     stt += t * t;
1015     stsx += t * over_temp_cal->model_data[i].offset[0];
1016     stsy += t * over_temp_cal->model_data[i].offset[1];
1017     stsz += t * over_temp_cal->model_data[i].offset[2];
1018   }
1019 
1020   // Calculates the linear model fit parameters.
1021   ASSERT(stt > 0);
1022   const float inv_stt = 1.0f / stt;
1023   temp_sensitivity[0] = stsx * inv_stt;
1024   sensor_intercept[0] = (sx - st * temp_sensitivity[0]) * inv_n;
1025   temp_sensitivity[1] = stsy * inv_stt;
1026   sensor_intercept[1] = (sy - st * temp_sensitivity[1]) * inv_n;
1027   temp_sensitivity[2] = stsz * inv_stt;
1028   sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_n;
1029 }
1030 
outlierCheck(struct OverTempCal * over_temp_cal,const float * offset,size_t axis_index,float temperature_celsius)1031 bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
1032                   size_t axis_index, float temperature_celsius) {
1033   ASSERT_NOT_NULL(over_temp_cal);
1034   ASSERT_NOT_NULL(offset);
1035 
1036   // If a model has been defined, then check to see if this offset could be a
1037   // potential outlier:
1038   if (over_temp_cal->temp_sensitivity[axis_index] < OTC_INITIAL_SENSITIVITY) {
1039     const float max_error_test = NANO_ABS(
1040         offset[axis_index] -
1041         (over_temp_cal->temp_sensitivity[axis_index] * temperature_celsius +
1042          over_temp_cal->sensor_intercept[axis_index]));
1043 
1044     if (max_error_test > over_temp_cal->max_error_limit) {
1045       return true;
1046     }
1047   }
1048 
1049   return false;
1050 }
1051 
1052 /////// DEBUG FUNCTION DEFINITIONS ////////////////////////////////////////////
1053 
1054 #ifdef OVERTEMPCAL_DBG_ENABLED
updateDebugData(struct OverTempCal * over_temp_cal)1055 void updateDebugData(struct OverTempCal* over_temp_cal) {
1056   ASSERT_NOT_NULL(over_temp_cal);
1057   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
1058 
1059   // Only update this data if debug printing is not currently in progress
1060   // (i.e., don't want to risk overwriting debug information that is actively
1061   // being reported).
1062   if (over_temp_cal->debug_state != OTC_IDLE) {
1063     return;
1064   }
1065 
1066   // Triggers a debug log printout.
1067   over_temp_cal->debug_print_trigger = true;
1068 
1069   // Initializes the debug data structure.
1070   memset(&over_temp_cal->debug_overtempcal, 0, sizeof(struct DebugOverTempCal));
1071 
1072   // Copies over the relevant data.
1073   memcpy(over_temp_cal->debug_overtempcal.temp_sensitivity,
1074          over_temp_cal->temp_sensitivity, 3 * sizeof(float));
1075   memcpy(over_temp_cal->debug_overtempcal.sensor_intercept,
1076          over_temp_cal->sensor_intercept, 3 * sizeof(float));
1077   memcpy(&over_temp_cal->debug_overtempcal.nearest_offset,
1078          over_temp_cal->nearest_offset, sizeof(struct OverTempCalDataPt));
1079 
1080   over_temp_cal->debug_overtempcal.num_model_pts = over_temp_cal->num_model_pts;
1081   over_temp_cal->debug_overtempcal.modelupdate_timestamp_nanos =
1082       over_temp_cal->modelupdate_timestamp_nanos;
1083   over_temp_cal->debug_overtempcal.temperature_celsius =
1084       over_temp_cal->temperature_celsius;
1085 
1086   // Computes the maximum error over all of the model data.
1087   overTempGetModelError(over_temp_cal,
1088                 over_temp_cal->debug_overtempcal.temp_sensitivity,
1089                 over_temp_cal->debug_overtempcal.sensor_intercept,
1090                 over_temp_cal->debug_overtempcal.max_error);
1091 }
1092 
overTempCalDebugPrint(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1093 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
1094                            uint64_t timestamp_nanos) {
1095   ASSERT_NOT_NULL(over_temp_cal);
1096 
1097   static enum OverTempCalDebugState next_state = 0;
1098   static uint64_t wait_timer = 0;
1099   static size_t i = 0;  // Counter.
1100 
1101   // This is a state machine that controls the reporting out of debug data.
1102   switch (over_temp_cal->debug_state) {
1103     case OTC_IDLE:
1104       // Wait for a trigger and start the debug printout sequence.
1105       if (over_temp_cal->debug_print_trigger) {
1106         CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "");
1107         CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG, "Debug Version: %s",
1108                       OVERTEMPCAL_DEBUG_VERSION_STRING);
1109         over_temp_cal->debug_print_trigger = false;  // Resets trigger.
1110         over_temp_cal->debug_state = OTC_PRINT_OFFSET;
1111       } else {
1112         over_temp_cal->debug_state = OTC_IDLE;
1113       }
1114       break;
1115 
1116     case OTC_WAIT_STATE:
1117       // This helps throttle the print statements.
1118       if (timestamp_nanos >= OVERTEMPCAL_WAIT_TIME_NANOS + wait_timer) {
1119         over_temp_cal->debug_state = next_state;
1120       }
1121       break;
1122 
1123     case OTC_PRINT_OFFSET:
1124       // Prints out the latest GyroCal offset estimate (input data).
1125       CAL_DEBUG_LOG(
1126           OVERTEMPCAL_REPORT_TAG,
1127           "Cal#|Offset|Temp|Time [mdps|C|nsec]: %lu, %s%d.%06d, "
1128           "%s%d.%06d, %s%d.%06d, %s%d.%03d, %llu",
1129           (unsigned long int)over_temp_cal->debug_num_estimates,
1130           CAL_ENCODE_FLOAT(
1131               over_temp_cal->debug_overtempcal.nearest_offset.offset[0] *
1132                   RAD_TO_MILLI_DEGREES,
1133               6),
1134           CAL_ENCODE_FLOAT(
1135               over_temp_cal->debug_overtempcal.nearest_offset.offset[1] *
1136                   RAD_TO_MILLI_DEGREES,
1137               6),
1138           CAL_ENCODE_FLOAT(
1139               over_temp_cal->debug_overtempcal.nearest_offset.offset[2] *
1140                   RAD_TO_MILLI_DEGREES,
1141               6),
1142           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.nearest_offset
1143                                .offset_temp_celsius,
1144                            6),
1145           (unsigned long long int)
1146               over_temp_cal->debug_overtempcal.nearest_offset.timestamp_nanos);
1147 
1148       wait_timer = timestamp_nanos;                 // Starts the wait timer.
1149       next_state = OTC_PRINT_MODEL_PARAMETERS;      // Sets the next state.
1150       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1151       break;
1152 
1153     case OTC_PRINT_MODEL_PARAMETERS:
1154       // Prints out the model parameters.
1155       CAL_DEBUG_LOG(OVERTEMPCAL_REPORT_TAG,
1156                     "Cal#|Sensitivity|Intercept [mdps/C|mdps]: %lu, %s%d.%06d, "
1157                     "%s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%06d, %s%d.%06d",
1158                     (unsigned long int)over_temp_cal->debug_num_estimates,
1159                     CAL_ENCODE_FLOAT(
1160                         over_temp_cal->debug_overtempcal.temp_sensitivity[0] *
1161                             RAD_TO_MILLI_DEGREES,
1162                         6),
1163                     CAL_ENCODE_FLOAT(
1164                         over_temp_cal->debug_overtempcal.temp_sensitivity[1] *
1165                             RAD_TO_MILLI_DEGREES,
1166                         6),
1167                     CAL_ENCODE_FLOAT(
1168                         over_temp_cal->debug_overtempcal.temp_sensitivity[2] *
1169                             RAD_TO_MILLI_DEGREES,
1170                         6),
1171                     CAL_ENCODE_FLOAT(
1172                         over_temp_cal->debug_overtempcal.sensor_intercept[0] *
1173                             RAD_TO_MILLI_DEGREES,
1174                         6),
1175                     CAL_ENCODE_FLOAT(
1176                         over_temp_cal->debug_overtempcal.sensor_intercept[1] *
1177                             RAD_TO_MILLI_DEGREES,
1178                         6),
1179                     CAL_ENCODE_FLOAT(
1180                         over_temp_cal->debug_overtempcal.sensor_intercept[2] *
1181                             RAD_TO_MILLI_DEGREES,
1182                         6));
1183 
1184       wait_timer = timestamp_nanos;                 // Starts the wait timer.
1185       next_state = OTC_PRINT_MODEL_ERROR;           // Sets the next state.
1186       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1187       break;
1188 
1189     case OTC_PRINT_MODEL_ERROR:
1190       // Computes the maximum error over all of the model data.
1191       CAL_DEBUG_LOG(
1192           OVERTEMPCAL_REPORT_TAG,
1193           "Cal#|#Updates|#ModelPts|Model Error|Update Time [mdps|nsec]: %lu, "
1194           "%lu, %lu, %s%d.%06d, %s%d.%06d, %s%d.%06d, %llu",
1195           (unsigned long int)over_temp_cal->debug_num_estimates,
1196           (unsigned long int)over_temp_cal->debug_num_model_updates,
1197           (unsigned long int)over_temp_cal->debug_overtempcal.num_model_pts,
1198           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[0] *
1199                                RAD_TO_MILLI_DEGREES,
1200                            6),
1201           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[1] *
1202                                RAD_TO_MILLI_DEGREES,
1203                            6),
1204           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[2] *
1205                                RAD_TO_MILLI_DEGREES,
1206                            6),
1207           (unsigned long long int)
1208               over_temp_cal->debug_overtempcal.modelupdate_timestamp_nanos);
1209 
1210       i = 0;                          // Resets the model data printer counter.
1211       wait_timer = timestamp_nanos;       // Starts the wait timer.
1212       next_state = OTC_PRINT_MODEL_DATA;  // Sets the next state.
1213       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1214       break;
1215 
1216     case OTC_PRINT_MODEL_DATA:
1217       // Prints out all of the model data.
1218       if (i < over_temp_cal->num_model_pts) {
1219         CAL_DEBUG_LOG(
1220             OVERTEMPCAL_REPORT_TAG,
1221             "  Model[%lu] [mdps|C|nsec] = %s%d.%06d, %s%d.%06d, %s%d.%06d, "
1222             "%s%d.%03d, %llu",
1223             (unsigned long int)i,
1224             CAL_ENCODE_FLOAT(
1225                 over_temp_cal->model_data[i].offset[0] * RAD_TO_MILLI_DEGREES,
1226                 6),
1227             CAL_ENCODE_FLOAT(
1228                 over_temp_cal->model_data[i].offset[1] * RAD_TO_MILLI_DEGREES,
1229                 6),
1230             CAL_ENCODE_FLOAT(
1231                 over_temp_cal->model_data[i].offset[2] * RAD_TO_MILLI_DEGREES,
1232                 6),
1233             CAL_ENCODE_FLOAT(over_temp_cal->model_data[i].offset_temp_celsius,
1234                              3),
1235             (unsigned long long int)over_temp_cal->model_data[i]
1236                 .timestamp_nanos);
1237 
1238         i++;
1239         wait_timer = timestamp_nanos;       // Starts the wait timer.
1240         next_state = OTC_PRINT_MODEL_DATA;  // Sets the next state.
1241         over_temp_cal->debug_state =
1242             OTC_WAIT_STATE;                 // First, go to wait state.
1243       } else {
1244         // Sends this state machine to its idle state.
1245         wait_timer = timestamp_nanos;  // Starts the wait timer.
1246         next_state = OTC_IDLE;         // Sets the next state.
1247         over_temp_cal->debug_state =
1248             OTC_WAIT_STATE;            // First, go to wait state.
1249       }
1250       break;
1251 
1252     default:
1253       // Sends this state machine to its idle state.
1254       wait_timer = timestamp_nanos;                 // Starts the wait timer.
1255       next_state = OTC_IDLE;                        // Sets the next state.
1256       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1257   }
1258 }
1259 #endif  // OVERTEMPCAL_DBG_ENABLED
1260