1 /*
2  * Copyright (C) 2017 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 <inttypes.h>
21 #include <math.h>
22 #include <string.h>
23 
24 #if defined(OVERTEMPCAL_DBG_ENABLED) || defined(OVERTEMPCAL_DBG_LOG_TEMP)
25 #include "calibration/util/cal_log.h"
26 #endif  // OVERTEMPCAL_DBG_ENABLED || OVERTEMPCAL_DBG_LOG_TEMP
27 
28 #include "util/nano_assert.h"
29 
30 /////// DEFINITIONS AND MACROS ////////////////////////////////////////////////
31 
32 // Value used to check whether OTC model data is near zero.
33 #define OTC_MODELDATA_NEAR_ZERO_TOL (1e-7f)
34 
35 // Defines the default weighting function for the linear model fit routine.
36 // Weighting = 10.0; for offsets newer than 5 minutes.
37 static const struct OverTempCalWeight kOtcDefaultWeight0 = {
38     .offset_age_nanos = MIN_TO_NANOS(5),
39     .weight = 10.0f,
40 };
41 
42 // Weighting = 0.1; for offsets newer than 15 minutes.
43 static const struct OverTempCalWeight kOtcDefaultWeight1 = {
44     .offset_age_nanos = MIN_TO_NANOS(15),
45     .weight = 0.1f,
46 };
47 
48 // The default weighting used for all older offsets.
49 #define OTC_MIN_WEIGHT_VALUE (0.04f)
50 
51 #ifdef OVERTEMPCAL_DBG_ENABLED
52 // A debug version label to help with tracking results.
53 #define OTC_DEBUG_VERSION_STRING "[Jan 10, 2018]"
54 
55 // The time interval used to throttle debug messaging (100msec).
56 #define OTC_WAIT_TIME_NANOS (SEC_TO_NANOS(0.1))
57 
58 // The time interval used to throttle temperature print messaging (1 second).
59 #define OTC_PRINT_TEMP_NANOS (SEC_TO_NANOS(1))
60 
61 // Sensor axis label definition with index correspondence: 0=X, 1=Y, 2=Z.
62 static const char kDebugAxisLabel[3] = "XYZ";
63 #endif  // OVERTEMPCAL_DBG_ENABLED
64 
65 /////// FORWARD DECLARATIONS //////////////////////////////////////////////////
66 
67 // Updates the latest received model estimate data.
68 static void setLatestEstimate(struct OverTempCal *over_temp_cal,
69                               const float *offset, float offset_temp_celsius);
70 
71 /*
72  * Determines if a new over-temperature model fit should be performed, and then
73  * updates the model as needed.
74  *
75  * INPUTS:
76  *   over_temp_cal:    Over-temp data structure.
77  *   timestamp_nanos:  Current timestamp for the model update.
78  */
79 static void computeModelUpdate(struct OverTempCal *over_temp_cal,
80                                uint64_t timestamp_nanos);
81 
82 /*
83  * Searches 'model_data' for the sensor offset estimate closest to the specified
84  * temperature. Sets the 'nearest_offset' pointer to the result.
85  */
86 static void findNearestEstimate(struct OverTempCal *over_temp_cal,
87                                 float temperature_celsius);
88 
89 /*
90  * Removes the "old" offset estimates from 'model_data' (i.e., eliminates the
91  * drift-compromised data).
92  */
93 static void removeStaleModelData(struct OverTempCal *over_temp_cal,
94                                  uint64_t timestamp_nanos);
95 
96 /*
97  * Removes the offset estimates from 'model_data' at index, 'model_index'.
98  * Returns 'true' if data was removed.
99  */
100 static bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
101                                    size_t model_index);
102 
103 /*
104  * Since it may take a while for an empty model to build up enough data to start
105  * producing new model parameter updates, the model collection can be
106  * jump-started by using the new model parameters to insert "fake" data in place
107  * of actual sensor offset data. The new model data 'offset_age_nanos' is set to
108  * zero.
109  */
110 static bool jumpStartModelData(struct OverTempCal *over_temp_cal);
111 
112 /*
113  * Computes a new model fit and provides updated model parameters for the
114  * over-temperature model data. Uses a simple weighting function determined from
115  * the age of the model data.
116  *
117  * INPUTS:
118  *   over_temp_cal:    Over-temp data structure.
119  * OUTPUTS:
120  *   temp_sensitivity: Updated modeled temperature sensitivity (array).
121  *   sensor_intercept: Updated model intercept (array).
122  *
123  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
124  *
125  * Reference: Press, William H. "15.2 Fitting Data to a Straight Line."
126  * Numerical Recipes: The Art of Scientific Computing. Cambridge, 1992.
127  */
128 static void updateModel(const struct OverTempCal *over_temp_cal,
129                         float *temp_sensitivity, float *sensor_intercept);
130 
131 /*
132  * Computes a new over-temperature compensated offset estimate based on the
133  * temperature specified by, 'temperature_celsius'.
134  *
135  * INPUTS:
136  *   over_temp_cal:        Over-temp data structure.
137  *   timestamp_nanos:      The current system timestamp.
138  *   temperature_celsius:  The sensor temperature to compensate the offset for.
139  */
140 static void updateCalOffset(struct OverTempCal *over_temp_cal,
141                             uint64_t timestamp_nanos,
142                             float temperature_celsius);
143 
144 /*
145  * Sets the new over-temperature compensated offset estimate vector and
146  * timestamp.
147  *
148  * INPUTS:
149  *   over_temp_cal:        Over-temp data structure.
150  *   compensated_offset:   The new temperature compensated offset array.
151  *   timestamp_nanos:      The current system timestamp.
152  *   temperature_celsius:  The sensor temperature to compensate the offset for.
153  */
154 static void setCompensatedOffset(struct OverTempCal *over_temp_cal,
155                                  const float *compensated_offset,
156                                  uint64_t timestamp_nanos,
157                                  float temperature_celsius);
158 
159 /*
160  * Checks new offset estimates to determine if they could be an outlier that
161  * should be rejected. Operates on a per-axis basis determined by 'axis_index'.
162  *
163  * INPUTS:
164  *   over_temp_cal:    Over-temp data structure.
165  *   offset:           Offset array.
166  *   axis_index:       Index of the axis to check (0=x, 1=y, 2=z).
167  *
168  * Returns 'true' if the deviation of the offset value from the linear model
169  * exceeds 'outlier_limit'.
170  */
171 static bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
172                          size_t axis_index, float temperature_celsius);
173 
174 // Sets the OTC model parameters to an "initialized" state.
175 static void resetOtcLinearModel(struct OverTempCal *over_temp_cal);
176 
177 // Checks that the input temperature value is within the valid range. If outside
178 // of range, then 'temperature_celsius' is coerced to within the limits.
179 static bool checkAndEnforceTemperatureRange(float *temperature_celsius);
180 
181 // Returns "true" if the candidate linear model parameters are within the valid
182 // range, and not all zeros.
183 static bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
184                                   float temp_sensitivity,
185                                   float sensor_intercept);
186 
187 // Returns "true" if 'offset' and 'offset_temp_celsius' is valid.
188 static bool isValidOtcOffset(const float *offset, float offset_temp_celsius);
189 
190 // Returns the least-squares weight based on the age of a particular offset
191 // estimate.
192 static float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal,
193                                        uint64_t offset_age_nanos);
194 
195 // Computes the age increment, adds it to the age of each OTC model data point,
196 // and resets the age update counter.
197 static void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal,
198                                   uint64_t timestamp_nanos);
199 
200 // Updates 'compensated_offset' using the linear OTC model.
201 static void compensateWithLinearModel(struct OverTempCal *over_temp_cal,
202                                       uint64_t timestamp_nanos,
203                                       float temperature_celsius);
204 
205 // Adds a linear extrapolated term to 'compensated_offset' (3-element array)
206 // based on the linear OTC model and 'delta_temp_celsius' (the difference
207 // between the current sensor temperature and the offset temperature associated
208 // with 'compensated_offset').
209 static void addLinearTemperatureExtrapolation(struct OverTempCal *over_temp_cal,
210                                               float *compensated_offset,
211                                               float delta_temp_celsius);
212 
213 // Provides an over-temperature compensated offset based on the 'estimate'.
214 static void compensateWithEstimate(struct OverTempCal *over_temp_cal,
215                                    uint64_t timestamp_nanos,
216                                    struct OverTempModelThreeAxis *estimate,
217                                    float temperature_celsius);
218 
219 // Evaluates the nearest-temperature compensation (with linear extrapolation
220 // term due to temperature), and compares it with the compensation due to
221 // just the linear model when 'compare_with_linear_model' is true, otherwise
222 // the comparison will be made with an extrapolated version of the current
223 // compensation value. The comparison tests whether the nearest-temperature
224 // estimate deviates from the linear-model (or current-compensated) value by
225 // more than 'jump_tolerance'. If a "jump" is detected, then it keeps the
226 // linear-model (or current-compensated) value.
227 static void compareAndCompensateWithNearest(struct OverTempCal *over_temp_cal,
228                                             uint64_t timestamp_nanos,
229                                             float temperature_celsius,
230                                             bool compare_to_linear_model);
231 
232 // Refreshes the OTC model to ensure that the most relevant model weighting is
233 // being used.
234 static void refreshOtcModel(struct OverTempCal *over_temp_cal,
235                             uint64_t timestamp_nanos);
236 
237 #ifdef OVERTEMPCAL_DBG_ENABLED
238 // This helper function stores all of the debug tracking information necessary
239 // for printing log messages.
240 static void updateDebugData(struct OverTempCal *over_temp_cal);
241 
242 // Helper function that creates tag strings useful for identifying specific
243 // debug output data (embedded system friendly; not all systems have 'sprintf').
244 // 'new_debug_tag' is any null-terminated string. Respect the total allowed
245 // length of the 'otc_debug_tag' string.
246 //   Constructs: "[" + <otc_debug_tag> + <new_debug_tag>
247 //   Example,
248 //     otc_debug_tag = "OVER_TEMP_CAL"
249 //     new_debug_tag = "INIT]"
250 //   Output: "[OVER_TEMP_CAL:INIT]"
251 static void createDebugTag(struct OverTempCal *over_temp_cal,
252                            const char *new_debug_tag);
253 #endif  // OVERTEMPCAL_DBG_ENABLED
254 
255 /////// FUNCTION DEFINITIONS //////////////////////////////////////////////////
256 
overTempCalInit(struct OverTempCal * over_temp_cal,const struct OverTempCalParameters * parameters)257 void overTempCalInit(struct OverTempCal *over_temp_cal,
258                      const struct OverTempCalParameters *parameters) {
259   ASSERT_NOT_NULL(over_temp_cal);
260 
261   // Clears OverTempCal memory.
262   memset(over_temp_cal, 0, sizeof(struct OverTempCal));
263 
264   // Initializes the pointers to important sensor offset estimates.
265   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
266   over_temp_cal->latest_offset = NULL;
267 
268   // Initializes the OTC linear model parameters.
269   resetOtcLinearModel(over_temp_cal);
270 
271   // Initializes the model identification parameters.
272   over_temp_cal->new_overtemp_model_available = false;
273   over_temp_cal->new_overtemp_offset_available = false;
274   over_temp_cal->min_num_model_pts = parameters->min_num_model_pts;
275   over_temp_cal->min_temp_update_period_nanos =
276       parameters->min_temp_update_period_nanos;
277   over_temp_cal->delta_temp_per_bin = parameters->delta_temp_per_bin;
278   over_temp_cal->jump_tolerance = parameters->jump_tolerance;
279   over_temp_cal->outlier_limit = parameters->outlier_limit;
280   over_temp_cal->age_limit_nanos = parameters->age_limit_nanos;
281   over_temp_cal->temp_sensitivity_limit = parameters->temp_sensitivity_limit;
282   over_temp_cal->sensor_intercept_limit = parameters->sensor_intercept_limit;
283   over_temp_cal->significant_offset_change =
284       parameters->significant_offset_change;
285   over_temp_cal->over_temp_enable = parameters->over_temp_enable;
286 
287   // Initializes the over-temperature compensated offset temperature.
288   over_temp_cal->compensated_offset.offset_temp_celsius =
289       INVALID_TEMPERATURE_CELSIUS;
290 
291 #ifdef OVERTEMPCAL_DBG_ENABLED
292   // Sets the default sensor descriptors for debugging.
293   overTempCalDebugDescriptors(over_temp_cal, "OVER_TEMP_CAL", "mDPS",
294                               RAD_TO_MDEG);
295 
296   createDebugTag(over_temp_cal, ":INIT]");
297   if (over_temp_cal->over_temp_enable) {
298     CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
299                   "Over-temperature compensation ENABLED.");
300   } else {
301     CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
302                   "Over-temperature compensation DISABLED.");
303   }
304 #endif  // OVERTEMPCAL_DBG_ENABLED
305 
306   // Defines the default weighting function for the linear model fit routine.
307   overTempValidateAndSetWeight(over_temp_cal, 0, &kOtcDefaultWeight0);
308   overTempValidateAndSetWeight(over_temp_cal, 1, &kOtcDefaultWeight1);
309 }
310 
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)311 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
312                          float offset_temp_celsius, uint64_t timestamp_nanos,
313                          const float *temp_sensitivity,
314                          const float *sensor_intercept, bool jump_start_model) {
315   ASSERT_NOT_NULL(over_temp_cal);
316   ASSERT_NOT_NULL(offset);
317   ASSERT_NOT_NULL(temp_sensitivity);
318   ASSERT_NOT_NULL(sensor_intercept);
319 
320   // Initializes the OTC linear model parameters.
321   resetOtcLinearModel(over_temp_cal);
322 
323   // Sets the model parameters if they are within the acceptable limits.
324   // Includes a check to reject input model parameters that may have been passed
325   // in as all zeros.
326   for (size_t i = 0; i < 3; i++) {
327     if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
328                               sensor_intercept[i])) {
329       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
330       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
331     }
332   }
333 
334   // Model "Jump-Start".
335   const bool model_jump_started =
336       jump_start_model ? jumpStartModelData(over_temp_cal) : false;
337 
338   if (!model_jump_started) {
339     // Checks that the new offset data is valid.
340     if (isValidOtcOffset(offset, offset_temp_celsius)) {
341       // Sets the initial over-temp calibration estimate.
342       memcpy(over_temp_cal->model_data[0].offset, offset,
343              sizeof(over_temp_cal->model_data[0].offset));
344       over_temp_cal->model_data[0].offset_temp_celsius = offset_temp_celsius;
345       over_temp_cal->model_data[0].offset_age_nanos = 0;
346       over_temp_cal->num_model_pts = 1;
347     } else {
348       // No valid offset data to load.
349       over_temp_cal->num_model_pts = 0;
350 #ifdef OVERTEMPCAL_DBG_ENABLED
351       createDebugTag(over_temp_cal, ":RECALL]");
352       CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
353                     "No valid sensor offset vector to load.");
354 #endif  // OVERTEMPCAL_DBG_ENABLED
355     }
356   }
357 
358   // If the new offset is valid, then it will be used as the current compensated
359   // offset, otherwise the current value will be kept.
360   if (isValidOtcOffset(offset, offset_temp_celsius)) {
361     memcpy(over_temp_cal->compensated_offset.offset, offset,
362            sizeof(over_temp_cal->compensated_offset.offset));
363     over_temp_cal->compensated_offset.offset_temp_celsius = offset_temp_celsius;
364     over_temp_cal->compensated_offset.offset_age_nanos = 0;
365   }
366 
367   // Resets the latest offset pointer. There are no new offset estimates to
368   // track yet.
369   over_temp_cal->latest_offset = NULL;
370 
371   // Sets the model and offset update times to the current timestamp.
372   over_temp_cal->last_offset_update_nanos = timestamp_nanos;
373   over_temp_cal->last_model_update_nanos = timestamp_nanos;
374 
375 #ifdef OVERTEMPCAL_DBG_ENABLED
376   // Prints the recalled model data.
377   createDebugTag(over_temp_cal, ":SET MODEL]");
378   CAL_DEBUG_LOG(
379       over_temp_cal->otc_debug_tag,
380       "Offset|Temp [%s|C]: " CAL_FORMAT_3DIGITS_TRIPLET
381       " | " CAL_FORMAT_3DIGITS,
382       over_temp_cal->otc_unit_tag,
383       CAL_ENCODE_FLOAT(offset[0] * over_temp_cal->otc_unit_conversion, 3),
384       CAL_ENCODE_FLOAT(offset[1] * over_temp_cal->otc_unit_conversion, 3),
385       CAL_ENCODE_FLOAT(offset[2] * over_temp_cal->otc_unit_conversion, 3),
386       CAL_ENCODE_FLOAT(offset_temp_celsius, 3));
387 
388   CAL_DEBUG_LOG(
389       over_temp_cal->otc_debug_tag,
390       "Sensitivity|Intercept [%s/C|%s]: " CAL_FORMAT_3DIGITS_TRIPLET
391       " | " CAL_FORMAT_3DIGITS_TRIPLET,
392       over_temp_cal->otc_unit_tag, over_temp_cal->otc_unit_tag,
393       CAL_ENCODE_FLOAT(temp_sensitivity[0] * over_temp_cal->otc_unit_conversion,
394                        3),
395       CAL_ENCODE_FLOAT(temp_sensitivity[1] * over_temp_cal->otc_unit_conversion,
396                        3),
397       CAL_ENCODE_FLOAT(temp_sensitivity[2] * over_temp_cal->otc_unit_conversion,
398                        3),
399       CAL_ENCODE_FLOAT(sensor_intercept[0] * over_temp_cal->otc_unit_conversion,
400                        3),
401       CAL_ENCODE_FLOAT(sensor_intercept[1] * over_temp_cal->otc_unit_conversion,
402                        3),
403       CAL_ENCODE_FLOAT(sensor_intercept[2] * over_temp_cal->otc_unit_conversion,
404                        3));
405 
406   // Resets the debug print machine to ensure that updateDebugData() can
407   // produce a debug report and interupt any ongoing report.
408   over_temp_cal->debug_state = OTC_IDLE;
409 
410   // Triggers a debug print out to view the new model parameters.
411   updateDebugData(over_temp_cal);
412 #endif  // OVERTEMPCAL_DBG_ENABLED
413 }
414 
overTempCalGetModel(struct OverTempCal * over_temp_cal,float * offset,float * offset_temp_celsius,uint64_t * timestamp_nanos,float * temp_sensitivity,float * sensor_intercept)415 void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset,
416                          float *offset_temp_celsius, uint64_t *timestamp_nanos,
417                          float *temp_sensitivity, float *sensor_intercept) {
418   ASSERT_NOT_NULL(over_temp_cal);
419   ASSERT_NOT_NULL(offset);
420   ASSERT_NOT_NULL(offset_temp_celsius);
421   ASSERT_NOT_NULL(timestamp_nanos);
422   ASSERT_NOT_NULL(temp_sensitivity);
423   ASSERT_NOT_NULL(sensor_intercept);
424 
425   // Gets the latest over-temp calibration model data.
426   memcpy(temp_sensitivity, over_temp_cal->temp_sensitivity,
427          sizeof(over_temp_cal->temp_sensitivity));
428   memcpy(sensor_intercept, over_temp_cal->sensor_intercept,
429          sizeof(over_temp_cal->sensor_intercept));
430   *timestamp_nanos = over_temp_cal->last_model_update_nanos;
431 
432   // Gets the latest temperature compensated offset estimate.
433   overTempCalGetOffset(over_temp_cal, offset_temp_celsius, offset);
434 }
435 
overTempCalSetModelData(struct OverTempCal * over_temp_cal,size_t data_length,uint64_t timestamp_nanos,const struct OverTempModelThreeAxis * model_data)436 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
437                              size_t data_length, uint64_t timestamp_nanos,
438                              const struct OverTempModelThreeAxis *model_data) {
439   ASSERT_NOT_NULL(over_temp_cal);
440   ASSERT_NOT_NULL(model_data);
441 
442   // Load only "good" data from the input 'model_data'.
443   over_temp_cal->num_model_pts = NANO_MIN(data_length, OTC_MODEL_SIZE);
444   size_t valid_data_count = 0;
445   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
446     if (isValidOtcOffset(model_data[i].offset,
447                          model_data[i].offset_temp_celsius)) {
448       memcpy(&over_temp_cal->model_data[i], &model_data[i],
449              sizeof(struct OverTempModelThreeAxis));
450       valid_data_count++;
451     }
452   }
453   over_temp_cal->num_model_pts = valid_data_count;
454 
455   // Initializes the OTC linear model parameters.
456   resetOtcLinearModel(over_temp_cal);
457 
458   // Computes and replaces the model fit parameters.
459   computeModelUpdate(over_temp_cal, timestamp_nanos);
460 
461   // Resets the latest offset pointer. There are no new offset estimates to
462   // track yet.
463   over_temp_cal->latest_offset = NULL;
464 
465   // Searches for the sensor offset estimate closest to the current temperature.
466   findNearestEstimate(over_temp_cal,
467                       over_temp_cal->compensated_offset.offset_temp_celsius);
468 
469   // Updates the current over-temperature compensated offset estimate.
470   updateCalOffset(over_temp_cal, timestamp_nanos,
471                   over_temp_cal->compensated_offset.offset_temp_celsius);
472 
473 #ifdef OVERTEMPCAL_DBG_ENABLED
474   // Prints the updated model data.
475   createDebugTag(over_temp_cal, ":SET MODEL DATA SET]");
476   CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
477                 "Over-temperature full model data set recalled.");
478 
479   // Resets the debug print machine to ensure that a new debug report will
480   // interupt any ongoing report.
481   over_temp_cal->debug_state = OTC_IDLE;
482 
483   // Triggers a log printout to show the updated sensor offset estimate.
484   updateDebugData(over_temp_cal);
485 #endif  // OVERTEMPCAL_DBG_ENABLED
486 }
487 
overTempCalGetModelData(struct OverTempCal * over_temp_cal,size_t * data_length,struct OverTempModelThreeAxis * model_data)488 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
489                              size_t *data_length,
490                              struct OverTempModelThreeAxis *model_data) {
491   ASSERT_NOT_NULL(over_temp_cal);
492   *data_length = over_temp_cal->num_model_pts;
493   memcpy(model_data, over_temp_cal->model_data,
494          over_temp_cal->num_model_pts * sizeof(struct OverTempModelThreeAxis));
495 }
496 
overTempCalGetOffset(struct OverTempCal * over_temp_cal,float * compensated_offset_temperature_celsius,float * compensated_offset)497 void overTempCalGetOffset(struct OverTempCal *over_temp_cal,
498                           float *compensated_offset_temperature_celsius,
499                           float *compensated_offset) {
500   memcpy(compensated_offset, over_temp_cal->compensated_offset.offset,
501          sizeof(over_temp_cal->compensated_offset.offset));
502   *compensated_offset_temperature_celsius =
503       over_temp_cal->compensated_offset.offset_temp_celsius;
504 }
505 
overTempCalRemoveOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float xi,float yi,float zi,float * xo,float * yo,float * zo)506 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
507                              uint64_t timestamp_nanos, float xi, float yi,
508                              float zi, float *xo, float *yo, float *zo) {
509   ASSERT_NOT_NULL(over_temp_cal);
510   ASSERT_NOT_NULL(xo);
511   ASSERT_NOT_NULL(yo);
512   ASSERT_NOT_NULL(zo);
513 
514   // Determines whether over-temp compensation will be applied.
515   if (over_temp_cal->over_temp_enable) {
516     // Removes the over-temperature compensated offset from the input sensor
517     // data.
518     *xo = xi - over_temp_cal->compensated_offset.offset[0];
519     *yo = yi - over_temp_cal->compensated_offset.offset[1];
520     *zo = zi - over_temp_cal->compensated_offset.offset[2];
521   } else {
522     *xo = xi;
523     *yo = yi;
524     *zo = zi;
525   }
526 }
527 
overTempCalNewModelUpdateAvailable(struct OverTempCal * over_temp_cal)528 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal) {
529   ASSERT_NOT_NULL(over_temp_cal);
530   const bool update_available = over_temp_cal->new_overtemp_model_available &&
531                                 over_temp_cal->over_temp_enable;
532 
533   // The 'new_overtemp_model_available' flag is reset when it is read here.
534   over_temp_cal->new_overtemp_model_available = false;
535 
536   return update_available;
537 }
538 
overTempCalNewOffsetAvailable(struct OverTempCal * over_temp_cal)539 bool overTempCalNewOffsetAvailable(struct OverTempCal *over_temp_cal) {
540   ASSERT_NOT_NULL(over_temp_cal);
541   const bool update_available = over_temp_cal->new_overtemp_offset_available &&
542                                 over_temp_cal->over_temp_enable;
543 
544   // The 'new_overtemp_offset_available' flag is reset when it is read here.
545   over_temp_cal->new_overtemp_offset_available = false;
546 
547   return update_available;
548 }
549 
overTempCalUpdateSensorEstimate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,const float * offset,float temperature_celsius)550 void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal,
551                                      uint64_t timestamp_nanos,
552                                      const float *offset,
553                                      float temperature_celsius) {
554   ASSERT_NOT_NULL(over_temp_cal);
555   ASSERT_NOT_NULL(offset);
556   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
557 
558   // Updates the age of each OTC model data point.
559   modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos);
560 
561   // Checks that the new offset data is valid, returns if bad.
562   if (!isValidOtcOffset(offset, temperature_celsius)) {
563     return;
564   }
565 
566   // Prevent a divide by zero below.
567   if (over_temp_cal->delta_temp_per_bin <= 0) {
568     return;
569   }
570 
571   // Ensures that the most relevant model weighting is being used.
572   refreshOtcModel(over_temp_cal, timestamp_nanos);
573 
574   // Checks whether this offset estimate is a likely outlier. A limit is placed
575   // on 'num_outliers', the previous number of successive rejects, to prevent
576   // too many back-to-back rejections.
577   if (over_temp_cal->num_outliers < OTC_MAX_OUTLIER_COUNT) {
578     if (outlierCheck(over_temp_cal, offset, 0, temperature_celsius) ||
579         outlierCheck(over_temp_cal, offset, 1, temperature_celsius) ||
580         outlierCheck(over_temp_cal, offset, 2, temperature_celsius)) {
581       // Increments the count of rejected outliers.
582       over_temp_cal->num_outliers++;
583 
584 #ifdef OVERTEMPCAL_DBG_ENABLED
585       createDebugTag(over_temp_cal, ":OUTLIER]");
586       CAL_DEBUG_LOG(
587           over_temp_cal->otc_debug_tag,
588           "Offset|Temperature|Time [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
589           ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
590           over_temp_cal->otc_unit_tag,
591           CAL_ENCODE_FLOAT(offset[0] * over_temp_cal->otc_unit_conversion, 3),
592           CAL_ENCODE_FLOAT(offset[1] * over_temp_cal->otc_unit_conversion, 3),
593           CAL_ENCODE_FLOAT(offset[2] * over_temp_cal->otc_unit_conversion, 3),
594           CAL_ENCODE_FLOAT(temperature_celsius, 3), timestamp_nanos);
595 #endif  // OVERTEMPCAL_DBG_ENABLED
596 
597       return;  // Outlier detected: skips adding this offset to the model.
598     } else {
599       // Resets the count of rejected outliers.
600       over_temp_cal->num_outliers = 0;
601     }
602   } else {
603     // Resets the count of rejected outliers.
604     over_temp_cal->num_outliers = 0;
605   }
606 
607   // Computes the temperature bin range data.
608   const int32_t bin_num =
609       NANO_FLOOR(temperature_celsius / over_temp_cal->delta_temp_per_bin);
610   const float temp_lo_check = bin_num * over_temp_cal->delta_temp_per_bin;
611   const float temp_hi_check = (bin_num + 1) * over_temp_cal->delta_temp_per_bin;
612 
613   // The rules for accepting new offset estimates into the 'model_data'
614   // collection:
615   //    1) The temperature domain is divided into bins each spanning
616   //       'delta_temp_per_bin'.
617   //    2) Find and replace the i'th 'model_data' estimate data if:
618   //          Let, bin_num = floor(temperature_celsius / delta_temp_per_bin)
619   //          temp_lo_check = bin_num * delta_temp_per_bin
620   //          temp_hi_check = (bin_num + 1) * delta_temp_per_bin
621   //          Check condition:
622   //          temp_lo_check <= model_data[i].offset_temp_celsius < temp_hi_check
623   bool replaced_one = false;
624   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
625     if (over_temp_cal->model_data[i].offset_temp_celsius < temp_hi_check &&
626         over_temp_cal->model_data[i].offset_temp_celsius >= temp_lo_check) {
627       // NOTE - The pointer to the new model data point is set here; the offset
628       // data is set below in the call to 'setLatestEstimate'.
629       over_temp_cal->latest_offset = &over_temp_cal->model_data[i];
630       replaced_one = true;
631       break;
632     }
633   }
634 
635   // NOTE - The pointer to the new model data point is set here; the offset
636   // data is set below in the call to 'setLatestEstimate'.
637   if (!replaced_one) {
638     if (over_temp_cal->num_model_pts < OTC_MODEL_SIZE) {
639       // 3) If nothing was replaced, and the 'model_data' buffer is not full
640       //    then add the estimate data to the array.
641       over_temp_cal->latest_offset =
642           &over_temp_cal->model_data[over_temp_cal->num_model_pts];
643       over_temp_cal->num_model_pts++;
644     } else {
645       // 4) Otherwise (nothing was replaced and buffer is full), replace the
646       //    oldest data with the incoming one.
647       over_temp_cal->latest_offset = &over_temp_cal->model_data[0];
648       for (size_t i = 1; i < over_temp_cal->num_model_pts; i++) {
649         if (over_temp_cal->latest_offset->offset_age_nanos <
650             over_temp_cal->model_data[i].offset_age_nanos) {
651           over_temp_cal->latest_offset = &over_temp_cal->model_data[i];
652         }
653       }
654     }
655   }
656 
657   // Updates the latest model estimate data.
658   setLatestEstimate(over_temp_cal, offset, temperature_celsius);
659 
660   // The latest offset estimate is the nearest temperature offset.
661   over_temp_cal->nearest_offset = over_temp_cal->latest_offset;
662 
663   // The rules for determining whether a new model fit is computed are:
664   //    1) A minimum number of data points must have been collected:
665   //          num_model_pts >= min_num_model_pts
666   //       NOTE: Collecting 'num_model_pts' and given that only one point is
667   //       kept per temperature bin (spanning a thermal range specified by
668   //       'delta_temp_per_bin') implies that model data covers at least,
669   //          model_temperature_span >= 'num_model_pts' * delta_temp_per_bin
670   //    2) ...shown in 'computeModelUpdate'.
671   if (over_temp_cal->num_model_pts >= over_temp_cal->min_num_model_pts) {
672     computeModelUpdate(over_temp_cal, timestamp_nanos);
673   }
674 
675   // Updates the current over-temperature compensated offset estimate.
676   updateCalOffset(over_temp_cal, timestamp_nanos, temperature_celsius);
677 
678 #ifdef OVERTEMPCAL_DBG_ENABLED
679   // Updates the total number of received sensor offset estimates.
680   over_temp_cal->debug_num_estimates++;
681 
682   // Triggers a log printout to show the updated sensor offset estimate.
683   updateDebugData(over_temp_cal);
684 #endif  // OVERTEMPCAL_DBG_ENABLED
685 }
686 
overTempCalSetTemperature(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius)687 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
688                                uint64_t timestamp_nanos,
689                                float temperature_celsius) {
690   ASSERT_NOT_NULL(over_temp_cal);
691 
692 #ifdef OVERTEMPCAL_DBG_ENABLED
693 #ifdef OVERTEMPCAL_DBG_LOG_TEMP
694   // Prints the sensor temperature trajectory for debugging purposes. This
695   // throttles the print statements (1Hz).
696   if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
697           timestamp_nanos, over_temp_cal->temperature_print_timer,
698           OTC_PRINT_TEMP_NANOS)) {
699     over_temp_cal->temperature_print_timer =
700         timestamp_nanos;  // Starts the wait timer.
701 
702     // Prints out temperature and the current timestamp.
703     createDebugTag(over_temp_cal, ":TEMP]");
704     CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
705                   "Temperature|Time [C|nsec] = " CAL_FORMAT_3DIGITS
706                   ", %" PRIu64,
707                   CAL_ENCODE_FLOAT(temperature_celsius, 3), timestamp_nanos);
708   }
709 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
710 #endif  // OVERTEMPCAL_DBG_ENABLED
711 
712   // Updates the age of each OTC model data point.
713   if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
714           timestamp_nanos, over_temp_cal->last_age_update_nanos,
715           OTC_MODEL_AGE_UPDATE_NANOS)) {
716     modelDataSetAgeUpdate(over_temp_cal, timestamp_nanos);
717   }
718 
719   // This check throttles new OTC offset compensation updates so that high data
720   // rate temperature samples do not cause excessive computational burden. Note,
721   // temperature sensor updates are expected to potentially increase the data
722   // processing load, however, computational load from new offset estimates is
723   // not a concern as they are a typically provided at a very low rate (< 1 Hz).
724   if (!NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
725           timestamp_nanos, over_temp_cal->last_offset_update_nanos,
726           over_temp_cal->min_temp_update_period_nanos)) {
727     return;  // Time interval too short, skip further data processing.
728   }
729 
730   // Checks that the offset temperature is within a valid range, saturates if
731   // outside.
732   checkAndEnforceTemperatureRange(&temperature_celsius);
733 
734   // Searches for the sensor offset estimate closest to the current temperature
735   // when the temperature has changed by more than +/-10% of the
736   // 'delta_temp_per_bin'.
737   if (over_temp_cal->num_model_pts > 0) {
738     if (NANO_ABS(over_temp_cal->last_temp_check_celsius - temperature_celsius) >
739         0.1f * over_temp_cal->delta_temp_per_bin) {
740       findNearestEstimate(over_temp_cal, temperature_celsius);
741       over_temp_cal->last_temp_check_celsius = temperature_celsius;
742     }
743   }
744 
745   // Updates the current over-temperature compensated offset estimate.
746   updateCalOffset(over_temp_cal, timestamp_nanos, temperature_celsius);
747 
748   // Sets the OTC offset compensation time check.
749   over_temp_cal->last_offset_update_nanos = timestamp_nanos;
750 }
751 
overTempValidateAndSetWeight(struct OverTempCal * over_temp_cal,size_t index,const struct OverTempCalWeight * new_otc_weight)752 bool overTempValidateAndSetWeight(
753     struct OverTempCal *over_temp_cal, size_t index,
754     const struct OverTempCalWeight *new_otc_weight) {
755   ASSERT_NOT_NULL(over_temp_cal);
756   ASSERT_NOT_NULL(new_otc_weight);
757 
758   // The input weighting coefficient must be positive.
759   if (new_otc_weight->weight <= 0.0f) {
760 #ifdef OVERTEMPCAL_DBG_ENABLED
761     createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]");
762     CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Invalid weight: Must > 0.");
763 #endif  // OVERTEMPCAL_DBG_ENABLED
764     return false;
765   }
766 
767   // Ensures that the 'index-1' weight's age is younger.
768   if (index == 0 ||
769       over_temp_cal->weighting_function[index - 1].offset_age_nanos <
770           new_otc_weight->offset_age_nanos) {
771     over_temp_cal->weighting_function[index] = *new_otc_weight;
772     return true;
773   }
774 
775 #ifdef OVERTEMPCAL_DBG_ENABLED
776   createDebugTag(over_temp_cal, ":WEIGHT_FUNCTION]");
777   CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Non monotonic weight age.");
778 #endif  // OVERTEMPCAL_DBG_ENABLED
779   return false;
780 }
781 
782 /////// LOCAL HELPER FUNCTION DEFINITIONS /////////////////////////////////////
783 
compensateWithLinearModel(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius)784 void compensateWithLinearModel(struct OverTempCal *over_temp_cal,
785                                uint64_t timestamp_nanos,
786                                float temperature_celsius) {
787   ASSERT_NOT_NULL(over_temp_cal);
788 
789   // Defaults to using the current compensated offset value.
790   float compensated_offset[3];
791   memcpy(compensated_offset, over_temp_cal->compensated_offset.offset,
792          sizeof(over_temp_cal->compensated_offset.offset));
793 
794   for (size_t index = 0; index < 3; index++) {
795     if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) {
796       // If a valid axis model is defined then the default compensation will
797       // use the linear model:
798       //   compensated_offset = (temp_sensitivity * temperature +
799       //   sensor_intercept)
800       compensated_offset[index] =
801           over_temp_cal->temp_sensitivity[index] * temperature_celsius +
802           over_temp_cal->sensor_intercept[index];
803     }
804   }
805 
806   // Sets the offset compensation vector, temperature, and timestamp.
807   setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos,
808                        temperature_celsius);
809 }
810 
addLinearTemperatureExtrapolation(struct OverTempCal * over_temp_cal,float * compensated_offset,float delta_temp_celsius)811 void addLinearTemperatureExtrapolation(struct OverTempCal *over_temp_cal,
812                                        float *compensated_offset,
813                                        float delta_temp_celsius) {
814   ASSERT_NOT_NULL(over_temp_cal);
815   ASSERT_NOT_NULL(compensated_offset);
816 
817   // Adds a delta term to the 'compensated_offset' using the temperature
818   // difference defined by 'delta_temp_celsius'.
819   for (size_t index = 0; index < 3; index++) {
820     if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) {
821       // If a valid axis model is defined, then use the linear model to assist
822       // with computing an extrapolated compensation term.
823       compensated_offset[index] +=
824           over_temp_cal->temp_sensitivity[index] * delta_temp_celsius;
825     }
826   }
827 }
828 
compensateWithEstimate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,struct OverTempModelThreeAxis * estimate,float temperature_celsius)829 void compensateWithEstimate(struct OverTempCal *over_temp_cal,
830                             uint64_t timestamp_nanos,
831                             struct OverTempModelThreeAxis *estimate,
832                             float temperature_celsius) {
833   ASSERT_NOT_NULL(over_temp_cal);
834   ASSERT_NOT_NULL(estimate);
835 
836   // Uses the most recent offset estimate for offset compensation.
837   float compensated_offset[3];
838   memcpy(compensated_offset, estimate->offset, sizeof(compensated_offset));
839 
840   // Checks that the offset temperature is valid.
841   if (estimate->offset_temp_celsius > INVALID_TEMPERATURE_CELSIUS) {
842     const float delta_temp_celsius =
843         temperature_celsius - estimate->offset_temp_celsius;
844 
845     // Adds a delta term to the compensated offset using the temperature
846     // difference defined by 'delta_temp_celsius'.
847     addLinearTemperatureExtrapolation(over_temp_cal, compensated_offset,
848                                       delta_temp_celsius);
849   }
850 
851   // Sets the offset compensation vector, temperature, and timestamp.
852   setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos,
853                        temperature_celsius);
854 }
855 
compareAndCompensateWithNearest(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius,bool compare_to_linear_model)856 void compareAndCompensateWithNearest(struct OverTempCal *over_temp_cal,
857                                      uint64_t timestamp_nanos,
858                                      float temperature_celsius,
859                                      bool compare_to_linear_model) {
860   ASSERT_NOT_NULL(over_temp_cal);
861   ASSERT_NOT_NULL(over_temp_cal->nearest_offset);
862 
863   // The default compensated offset is the nearest-temperature offset vector.
864   float compensated_offset[3];
865   memcpy(compensated_offset, over_temp_cal->nearest_offset->offset,
866          sizeof(compensated_offset));
867   const float compensated_offset_temperature_celsius =
868       over_temp_cal->nearest_offset->offset_temp_celsius;
869 
870   for (size_t index = 0; index < 3; index++) {
871     if (over_temp_cal->temp_sensitivity[index] < OTC_INITIAL_SENSITIVITY) {
872       // If a valid axis model is defined, then use the linear model to assist
873       // with computing an extrapolated compensation term.
874       float delta_temp_celsius =
875           temperature_celsius - compensated_offset_temperature_celsius;
876       compensated_offset[index] +=
877           over_temp_cal->temp_sensitivity[index] * delta_temp_celsius;
878 
879       // Computes the test offset (based on the linear model or current offset).
880       float test_offset;
881       if (compare_to_linear_model) {
882         test_offset =
883             over_temp_cal->temp_sensitivity[index] * temperature_celsius +
884             over_temp_cal->sensor_intercept[index];
885       } else {
886         // Adds a delta term to the compensated offset using the temperature
887         // difference defined by 'delta_temp_celsius'.
888         if (over_temp_cal->compensated_offset.offset_temp_celsius <=
889             INVALID_TEMPERATURE_CELSIUS) {
890           // If temperature is invalid, then skip further processing.
891           break;
892         }
893         delta_temp_celsius =
894             temperature_celsius -
895             over_temp_cal->compensated_offset.offset_temp_celsius;
896         test_offset =
897             over_temp_cal->compensated_offset.offset[index] +
898             over_temp_cal->temp_sensitivity[index] * delta_temp_celsius;
899       }
900 
901       // Checks for "jumps" in the candidate compensated offset. If detected,
902       // then 'test_offset' is used for the offset update.
903       if (NANO_ABS(test_offset - compensated_offset[index]) >=
904           over_temp_cal->jump_tolerance) {
905         compensated_offset[index] = test_offset;
906       }
907     }
908   }
909 
910   // Sets the offset compensation vector, temperature, and timestamp.
911   setCompensatedOffset(over_temp_cal, compensated_offset, timestamp_nanos,
912                        temperature_celsius);
913 }
914 
updateCalOffset(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos,float temperature_celsius)915 void updateCalOffset(struct OverTempCal *over_temp_cal,
916                      uint64_t timestamp_nanos, float temperature_celsius) {
917   ASSERT_NOT_NULL(over_temp_cal);
918 
919   // If 'temperature_celsius' is invalid, then no changes to the compensated
920   // offset are computed.
921   if (temperature_celsius <= INVALID_TEMPERATURE_CELSIUS) {
922     return;
923   }
924 
925   // Removes very old data from the collected model estimates (i.e.,
926   // eliminates drift-compromised data). Only does this when there is more
927   // than one estimate in the model (i.e., don't want to remove all data, even
928   // if it is very old [something is likely better than nothing]).
929   if ((timestamp_nanos >=
930        OTC_STALE_CHECK_TIME_NANOS + over_temp_cal->stale_data_timer_nanos) &&
931       over_temp_cal->num_model_pts > 1) {
932     over_temp_cal->stale_data_timer_nanos = timestamp_nanos;  // Resets timer.
933     removeStaleModelData(over_temp_cal, timestamp_nanos);
934   }
935 
936   // Ensures that the most relevant model weighting is being used.
937   refreshOtcModel(over_temp_cal, timestamp_nanos);
938 
939   // ---------------------------------------------------------------------------
940   // The following boolean expressions help determine how OTC offset updates
941   // are computed below.
942 
943   // The nearest-temperature offset estimate is valid if the model data set is
944   // not empty.
945   const bool model_points_available = (over_temp_cal->num_model_pts > 0);
946 
947   // To properly evaluate the logic paths that use the latest and nearest offset
948   // data below, the current age of the nearest and latest offset estimates are
949   // computed.
950   uint64_t latest_offset_age_nanos = 0;
951   if (over_temp_cal->latest_offset != NULL) {
952     latest_offset_age_nanos =
953         (over_temp_cal->last_age_update_nanos < timestamp_nanos)
954             ? over_temp_cal->latest_offset->offset_age_nanos +
955                   timestamp_nanos - over_temp_cal->last_age_update_nanos
956             : over_temp_cal->latest_offset->offset_age_nanos;
957   }
958 
959   uint64_t nearest_offset_age_nanos = 0;
960   if (over_temp_cal->nearest_offset != NULL) {
961     nearest_offset_age_nanos =
962         (over_temp_cal->last_age_update_nanos < timestamp_nanos)
963             ? over_temp_cal->nearest_offset->offset_age_nanos +
964                   timestamp_nanos - over_temp_cal->last_age_update_nanos
965             : over_temp_cal->nearest_offset->offset_age_nanos;
966   }
967 
968   // True when the latest offset estimate will be used to compute a sensor
969   // offset calibration estimate.
970   const bool use_latest_offset_compensation =
971       over_temp_cal->latest_offset != NULL && model_points_available &&
972       latest_offset_age_nanos <= OTC_USE_RECENT_OFFSET_TIME_NANOS;
973 
974   // True when the conditions are met to use the nearest-temperature offset to
975   // compute a sensor offset calibration estimate.
976   //  The nearest-temperature offset:
977   //    i.  Must be defined.
978   //    ii. Offset temperature must be within a small neighborhood of the
979   //        current measured temperature (+/- 'delta_temp_per_bin').
980   const bool can_compensate_with_nearest =
981       model_points_available && over_temp_cal->nearest_offset != NULL &&
982       NANO_ABS(temperature_celsius -
983                over_temp_cal->nearest_offset->offset_temp_celsius) <
984           over_temp_cal->delta_temp_per_bin;
985 
986   // True if the last received sensor offset estimate is old or non-existent.
987   const bool latest_model_point_not_relevant =
988       (over_temp_cal->latest_offset == NULL) ||
989       (over_temp_cal->latest_offset != NULL &&
990        latest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS);
991 
992   // True if the nearest-temperature offset estimate is old or non-existent.
993   const bool nearest_model_point_not_relevant =
994       (over_temp_cal->nearest_offset == NULL) ||
995       (over_temp_cal->nearest_offset != NULL &&
996        nearest_offset_age_nanos >= OTC_OFFSET_IS_STALE_NANOS);
997 
998   // ---------------------------------------------------------------------------
999   // The following conditional expressions govern new OTC offset updates.
1000 
1001   if (!model_points_available) {
1002     // Computes the compensation using just the linear model if available,
1003     // otherwise the current compensated offset vector will be kept.
1004     compensateWithLinearModel(over_temp_cal, timestamp_nanos,
1005                               temperature_celsius);
1006     return;  // no further calculations, exit early.
1007   }
1008 
1009   if (use_latest_offset_compensation) {
1010     // Computes the compensation using the latest received offset estimate plus
1011     // a term based on linear extrapolation from the offset temperature to the
1012     // current measured temperature (if a linear model is defined).
1013     compensateWithEstimate(over_temp_cal, timestamp_nanos,
1014                            over_temp_cal->latest_offset, temperature_celsius);
1015     return;  // no further calculations, exit early.
1016   }
1017 
1018   if (can_compensate_with_nearest) {
1019     // Evaluates the nearest-temperature compensation (with a linear
1020     // extrapolation term), and compares it with the compensation due to just
1021     // the linear model, when 'compare_with_linear_model' is true. Otherwise,
1022     // the comparison will be made with an extrapolated version of the current
1023     // compensation value. The comparison determines whether the
1024     // nearest-temperature estimate deviates from the linear-model (or
1025     // current-compensated) value by more than 'jump_tolerance'. If a "jump" is
1026     // detected, then it keeps the linear-model (or current-compensated) value.
1027     const bool compare_with_linear_model = nearest_model_point_not_relevant;
1028     compareAndCompensateWithNearest(over_temp_cal, timestamp_nanos,
1029                                     temperature_celsius,
1030                                     compare_with_linear_model);
1031   } else {
1032     if (latest_model_point_not_relevant) {
1033       // If the nearest-temperature offset can't be used for compensation and
1034       // the latest offset is stale (in this case, the overall model trend may
1035       // be more useful for compensation than extending the most recent vector),
1036       // then this resorts to using only the linear model (if defined).
1037       compensateWithLinearModel(over_temp_cal, timestamp_nanos,
1038                                 temperature_celsius);
1039     } else {
1040       // If the nearest-temperature offset can't be used for compensation and
1041       // the latest offset is fairly recent, then the compensated offset is
1042       // based on the linear extrapolation of the current compensation vector.
1043       compensateWithEstimate(over_temp_cal, timestamp_nanos,
1044                              &over_temp_cal->compensated_offset,
1045                              temperature_celsius);
1046     }
1047   }
1048 }
1049 
setCompensatedOffset(struct OverTempCal * over_temp_cal,const float * compensated_offset,uint64_t timestamp_nanos,float temperature_celsius)1050 void setCompensatedOffset(struct OverTempCal *over_temp_cal,
1051                           const float *compensated_offset,
1052                           uint64_t timestamp_nanos, float temperature_celsius) {
1053   ASSERT_NOT_NULL(over_temp_cal);
1054   ASSERT_NOT_NULL(compensated_offset);
1055 
1056   // If the 'compensated_offset' value has changed significantly, then set
1057   // 'new_overtemp_offset_available' true.
1058   bool new_overtemp_offset_available = false;
1059   for (size_t i = 0; i < 3; i++) {
1060     if (NANO_ABS(over_temp_cal->compensated_offset.offset[i] -
1061                  compensated_offset[i]) >=
1062         over_temp_cal->significant_offset_change) {
1063       new_overtemp_offset_available |= true;
1064       break;
1065     }
1066   }
1067   over_temp_cal->new_overtemp_offset_available |= new_overtemp_offset_available;
1068 
1069   // If the offset has changed significantly, then the offset compensation
1070   // vector is updated.
1071   if (new_overtemp_offset_available) {
1072     memcpy(over_temp_cal->compensated_offset.offset, compensated_offset,
1073            sizeof(over_temp_cal->compensated_offset.offset));
1074     over_temp_cal->compensated_offset.offset_temp_celsius = temperature_celsius;
1075   }
1076 }
1077 
setLatestEstimate(struct OverTempCal * over_temp_cal,const float * offset,float offset_temp_celsius)1078 void setLatestEstimate(struct OverTempCal *over_temp_cal, const float *offset,
1079                        float offset_temp_celsius) {
1080   ASSERT_NOT_NULL(over_temp_cal);
1081   ASSERT_NOT_NULL(offset);
1082 
1083   if (over_temp_cal->latest_offset != NULL) {
1084     // Sets the latest over-temp calibration estimate.
1085     memcpy(over_temp_cal->latest_offset->offset, offset,
1086            sizeof(over_temp_cal->latest_offset->offset));
1087     over_temp_cal->latest_offset->offset_temp_celsius = offset_temp_celsius;
1088     over_temp_cal->latest_offset->offset_age_nanos = 0;
1089   }
1090 }
1091 
refreshOtcModel(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1092 void refreshOtcModel(struct OverTempCal *over_temp_cal,
1093                      uint64_t timestamp_nanos) {
1094   ASSERT_NOT_NULL(over_temp_cal);
1095   if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
1096           timestamp_nanos, over_temp_cal->last_model_update_nanos,
1097           OTC_REFRESH_MODEL_NANOS)) {
1098     // Checks the time since the last computed model and recalculates the model
1099     // if necessary. This ensures that waking up after a long period of time
1100     // allows the properly weighted OTC model to be used. As the estimates age,
1101     // the weighting will become more uniform and the model will fit the whole
1102     // set uniformly as a better approximation to the expected temperature
1103     // sensitivity; Younger estimates will fit tighter to emphasize a more
1104     // localized fit of the temp sensitivity function.
1105     computeModelUpdate(over_temp_cal, timestamp_nanos);
1106     over_temp_cal->last_model_update_nanos = timestamp_nanos;
1107   }
1108 }
1109 
computeModelUpdate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1110 void computeModelUpdate(struct OverTempCal *over_temp_cal,
1111                         uint64_t timestamp_nanos) {
1112   ASSERT_NOT_NULL(over_temp_cal);
1113 
1114   // Ensures that the minimum number of points required for a model fit has been
1115   // satisfied.
1116   if (over_temp_cal->num_model_pts < over_temp_cal->min_num_model_pts) return;
1117 
1118   // Updates the linear model fit.
1119   float temp_sensitivity[3];
1120   float sensor_intercept[3];
1121   updateModel(over_temp_cal, temp_sensitivity, sensor_intercept);
1122 
1123   //    2) A new set of model parameters are accepted if:
1124   //         i. The model fit parameters must be within certain absolute bounds:
1125   //              a. |temp_sensitivity| < temp_sensitivity_limit
1126   //              b. |sensor_intercept| < sensor_intercept_limit
1127   // NOTE: Model parameter updates are not qualified against model fit error
1128   // here to protect against the case where there is large change in the
1129   // temperature characteristic either during runtime (e.g., temperature
1130   // conditioning due to hysteresis) or as a result of loading a poor model data
1131   // set. Otherwise, a lockout condition could occur where the entire model
1132   // data set would need to be replaced in order to bring the model fit error
1133   // below the error limit and allow a successful model update.
1134   bool updated_one = false;
1135   for (size_t i = 0; i < 3; i++) {
1136     if (isValidOtcLinearModel(over_temp_cal, temp_sensitivity[i],
1137                               sensor_intercept[i])) {
1138       over_temp_cal->temp_sensitivity[i] = temp_sensitivity[i];
1139       over_temp_cal->sensor_intercept[i] = sensor_intercept[i];
1140       updated_one = true;
1141     } else {
1142 #ifdef OVERTEMPCAL_DBG_ENABLED
1143       createDebugTag(over_temp_cal, ":REJECT]");
1144       CAL_DEBUG_LOG(
1145           over_temp_cal->otc_debug_tag,
1146           "%c-Axis Parameters|Time [%s/C|%s|nsec]: " CAL_FORMAT_3DIGITS
1147           ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
1148           kDebugAxisLabel[i], over_temp_cal->otc_unit_tag,
1149           over_temp_cal->otc_unit_tag,
1150           CAL_ENCODE_FLOAT(
1151               temp_sensitivity[i] * over_temp_cal->otc_unit_conversion, 3),
1152           CAL_ENCODE_FLOAT(
1153               sensor_intercept[i] * over_temp_cal->otc_unit_conversion, 3),
1154           timestamp_nanos);
1155 #endif  // OVERTEMPCAL_DBG_ENABLED
1156     }
1157   }
1158 
1159   // If at least one axis updated, then consider this a valid model update.
1160   if (updated_one) {
1161     // Resets the OTC model compensation update time and sets the update flag.
1162     over_temp_cal->last_model_update_nanos = timestamp_nanos;
1163     over_temp_cal->new_overtemp_model_available = true;
1164 
1165 #ifdef OVERTEMPCAL_DBG_ENABLED
1166     // Updates the total number of model updates.
1167     over_temp_cal->debug_num_model_updates++;
1168 #endif  // OVERTEMPCAL_DBG_ENABLED
1169   }
1170 }
1171 
findNearestEstimate(struct OverTempCal * over_temp_cal,float temperature_celsius)1172 void findNearestEstimate(struct OverTempCal *over_temp_cal,
1173                          float temperature_celsius) {
1174   ASSERT_NOT_NULL(over_temp_cal);
1175 
1176   // If 'temperature_celsius' is invalid, then do not search.
1177   if (temperature_celsius <= INVALID_TEMPERATURE_CELSIUS) {
1178     return;
1179   }
1180 
1181   // Performs a brute force search for the estimate nearest
1182   // 'temperature_celsius'.
1183   float dtemp_new = 0.0f;
1184   float dtemp_old = FLT_MAX;
1185   over_temp_cal->nearest_offset = &over_temp_cal->model_data[0];
1186   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
1187     dtemp_new = NANO_ABS(over_temp_cal->model_data[i].offset_temp_celsius -
1188                          temperature_celsius);
1189     if (dtemp_new < dtemp_old) {
1190       over_temp_cal->nearest_offset = &over_temp_cal->model_data[i];
1191       dtemp_old = dtemp_new;
1192     }
1193   }
1194 }
1195 
removeStaleModelData(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1196 void removeStaleModelData(struct OverTempCal *over_temp_cal,
1197                           uint64_t timestamp_nanos) {
1198   ASSERT_NOT_NULL(over_temp_cal);
1199 
1200   bool removed_one = false;
1201   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
1202     if (over_temp_cal->model_data[i].offset_age_nanos >=
1203         over_temp_cal->age_limit_nanos) {
1204       // If the latest offset was removed, then indicate this by setting it to
1205       // NULL.
1206       if (over_temp_cal->latest_offset == &over_temp_cal->model_data[i]) {
1207         over_temp_cal->latest_offset = NULL;
1208       }
1209       removed_one |= removeModelDataByIndex(over_temp_cal, i);
1210     }
1211   }
1212 
1213   if (removed_one) {
1214     // If anything was removed, then this attempts to recompute the model.
1215     computeModelUpdate(over_temp_cal, timestamp_nanos);
1216 
1217     // Searches for the sensor offset estimate closest to the current
1218     // temperature.
1219     findNearestEstimate(over_temp_cal,
1220                         over_temp_cal->compensated_offset.offset_temp_celsius);
1221   }
1222 }
1223 
removeModelDataByIndex(struct OverTempCal * over_temp_cal,size_t model_index)1224 bool removeModelDataByIndex(struct OverTempCal *over_temp_cal,
1225                             size_t model_index) {
1226   ASSERT_NOT_NULL(over_temp_cal);
1227 
1228   // This function will not remove all of the model data. At least one model
1229   // sample will be left.
1230   if (over_temp_cal->num_model_pts <= 1) {
1231     return false;
1232   }
1233 
1234 #ifdef OVERTEMPCAL_DBG_ENABLED
1235   createDebugTag(over_temp_cal, ":REMOVE]");
1236   CAL_DEBUG_LOG(
1237       over_temp_cal->otc_debug_tag,
1238       "Offset|Temp|Age [%s|C|nsec]: " CAL_FORMAT_3DIGITS_TRIPLET
1239       ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
1240       over_temp_cal->otc_unit_tag,
1241       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[0] *
1242                            over_temp_cal->otc_unit_conversion,
1243                        3),
1244       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
1245                            over_temp_cal->otc_unit_conversion,
1246                        3),
1247       CAL_ENCODE_FLOAT(over_temp_cal->model_data[model_index].offset[1] *
1248                            over_temp_cal->otc_unit_conversion,
1249                        3),
1250       CAL_ENCODE_FLOAT(
1251           over_temp_cal->model_data[model_index].offset_temp_celsius, 3),
1252       over_temp_cal->model_data[model_index].offset_age_nanos);
1253 #endif  // OVERTEMPCAL_DBG_ENABLED
1254 
1255   // Remove the model data at 'model_index'.
1256   for (size_t i = model_index; i < over_temp_cal->num_model_pts - 1; i++) {
1257     memcpy(&over_temp_cal->model_data[i], &over_temp_cal->model_data[i + 1],
1258            sizeof(struct OverTempModelThreeAxis));
1259   }
1260   over_temp_cal->num_model_pts--;
1261 
1262   return true;
1263 }
1264 
jumpStartModelData(struct OverTempCal * over_temp_cal)1265 bool jumpStartModelData(struct OverTempCal *over_temp_cal) {
1266   ASSERT_NOT_NULL(over_temp_cal);
1267   ASSERT(over_temp_cal->delta_temp_per_bin > 0);
1268 
1269   // Prevent a divide by zero below.
1270   if (over_temp_cal->delta_temp_per_bin <= 0) {
1271     return false;
1272   }
1273 
1274   // In normal operation the offset estimates enter into the 'model_data' array
1275   // complete (i.e., x, y, z values are all provided). Therefore, the jumpstart
1276   // data produced here requires that the model parameters have all been fully
1277   // defined and are all within the valid range.
1278   for (size_t i = 0; i < 3; i++) {
1279     if (!isValidOtcLinearModel(over_temp_cal,
1280                                over_temp_cal->temp_sensitivity[i],
1281                                over_temp_cal->sensor_intercept[i])) {
1282       return false;
1283     }
1284   }
1285 
1286   // Any pre-existing model data points will be overwritten.
1287   over_temp_cal->num_model_pts = 0;
1288 
1289   // This defines the minimum contiguous set of points to allow a model update
1290   // when the next offset estimate is received. They are placed at a common
1291   // temperature range that is likely to get replaced with actual data soon.
1292   const int32_t start_bin_num = NANO_FLOOR(JUMPSTART_START_TEMP_CELSIUS /
1293                                            over_temp_cal->delta_temp_per_bin);
1294   float offset_temp_celsius =
1295       (start_bin_num + 0.5f) * over_temp_cal->delta_temp_per_bin;
1296 
1297   for (size_t i = 0; i < over_temp_cal->min_num_model_pts; i++) {
1298     for (size_t j = 0; j < 3; j++) {
1299       over_temp_cal->model_data[i].offset[j] =
1300           over_temp_cal->temp_sensitivity[j] * offset_temp_celsius +
1301           over_temp_cal->sensor_intercept[j];
1302     }
1303     over_temp_cal->model_data[i].offset_temp_celsius = offset_temp_celsius;
1304     over_temp_cal->model_data[i].offset_age_nanos = 0;
1305 
1306     offset_temp_celsius += over_temp_cal->delta_temp_per_bin;
1307     over_temp_cal->num_model_pts++;
1308   }
1309 
1310 #ifdef OVERTEMPCAL_DBG_ENABLED
1311   createDebugTag(over_temp_cal, ":INIT]");
1312   if (over_temp_cal->num_model_pts > 0) {
1313     CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
1314                   "Model Jump-Start:  #Points = %zu.",
1315                   over_temp_cal->num_model_pts);
1316   }
1317 #endif  // OVERTEMPCAL_DBG_ENABLED
1318 
1319   return (over_temp_cal->num_model_pts > 0);
1320 }
1321 
updateModel(const struct OverTempCal * over_temp_cal,float * temp_sensitivity,float * sensor_intercept)1322 void updateModel(const struct OverTempCal *over_temp_cal,
1323                  float *temp_sensitivity, float *sensor_intercept) {
1324   ASSERT_NOT_NULL(over_temp_cal);
1325   ASSERT_NOT_NULL(temp_sensitivity);
1326   ASSERT_NOT_NULL(sensor_intercept);
1327   ASSERT(over_temp_cal->num_model_pts > 0);
1328 
1329   float sw = 0.0f;
1330   float st = 0.0f, stt = 0.0f;
1331   float sx = 0.0f, stsx = 0.0f;
1332   float sy = 0.0f, stsy = 0.0f;
1333   float sz = 0.0f, stsz = 0.0f;
1334   float weight = 1.0f;
1335 
1336   // First pass computes the weighted mean values.
1337   const size_t n = over_temp_cal->num_model_pts;
1338   for (size_t i = 0; i < n; ++i) {
1339     weight = evaluateWeightingFunction(
1340         over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos);
1341 
1342     sw += weight;
1343     st += over_temp_cal->model_data[i].offset_temp_celsius * weight;
1344     sx += over_temp_cal->model_data[i].offset[0] * weight;
1345     sy += over_temp_cal->model_data[i].offset[1] * weight;
1346     sz += over_temp_cal->model_data[i].offset[2] * weight;
1347   }
1348 
1349   // Second pass computes the mean corrected second moment values.
1350   ASSERT(sw > 0.0f);
1351   const float inv_sw = 1.0f / sw;
1352   for (size_t i = 0; i < n; ++i) {
1353     weight = evaluateWeightingFunction(
1354         over_temp_cal, over_temp_cal->model_data[i].offset_age_nanos);
1355 
1356     const float t =
1357         over_temp_cal->model_data[i].offset_temp_celsius - st * inv_sw;
1358     stt += weight * t * t;
1359     stsx += t * over_temp_cal->model_data[i].offset[0] * weight;
1360     stsy += t * over_temp_cal->model_data[i].offset[1] * weight;
1361     stsz += t * over_temp_cal->model_data[i].offset[2] * weight;
1362   }
1363 
1364   // Calculates the linear model fit parameters.
1365   ASSERT(stt > 0.0f);
1366   const float inv_stt = 1.0f / stt;
1367   temp_sensitivity[0] = stsx * inv_stt;
1368   sensor_intercept[0] = (sx - st * temp_sensitivity[0]) * inv_sw;
1369   temp_sensitivity[1] = stsy * inv_stt;
1370   sensor_intercept[1] = (sy - st * temp_sensitivity[1]) * inv_sw;
1371   temp_sensitivity[2] = stsz * inv_stt;
1372   sensor_intercept[2] = (sz - st * temp_sensitivity[2]) * inv_sw;
1373 }
1374 
outlierCheck(struct OverTempCal * over_temp_cal,const float * offset,size_t axis_index,float temperature_celsius)1375 bool outlierCheck(struct OverTempCal *over_temp_cal, const float *offset,
1376                   size_t axis_index, float temperature_celsius) {
1377   ASSERT_NOT_NULL(over_temp_cal);
1378   ASSERT_NOT_NULL(offset);
1379 
1380   // If a model has been defined, then check to see if this offset could be a
1381   // potential outlier:
1382   if (over_temp_cal->temp_sensitivity[axis_index] < OTC_INITIAL_SENSITIVITY) {
1383     const float outlier_test = NANO_ABS(
1384         offset[axis_index] -
1385         (over_temp_cal->temp_sensitivity[axis_index] * temperature_celsius +
1386          over_temp_cal->sensor_intercept[axis_index]));
1387 
1388     if (outlier_test > over_temp_cal->outlier_limit) {
1389       return true;
1390     }
1391   }
1392 
1393   return false;
1394 }
1395 
resetOtcLinearModel(struct OverTempCal * over_temp_cal)1396 void resetOtcLinearModel(struct OverTempCal *over_temp_cal) {
1397   ASSERT_NOT_NULL(over_temp_cal);
1398 
1399   // Sets the temperature sensitivity model parameters to
1400   // OTC_INITIAL_SENSITIVITY to indicate that the model is in an "initial"
1401   // state.
1402   over_temp_cal->temp_sensitivity[0] = OTC_INITIAL_SENSITIVITY;
1403   over_temp_cal->temp_sensitivity[1] = OTC_INITIAL_SENSITIVITY;
1404   over_temp_cal->temp_sensitivity[2] = OTC_INITIAL_SENSITIVITY;
1405   memset(over_temp_cal->sensor_intercept, 0,
1406          sizeof(over_temp_cal->sensor_intercept));
1407 }
1408 
checkAndEnforceTemperatureRange(float * temperature_celsius)1409 bool checkAndEnforceTemperatureRange(float *temperature_celsius) {
1410   if (*temperature_celsius > OTC_TEMP_MAX_CELSIUS) {
1411     *temperature_celsius = OTC_TEMP_MAX_CELSIUS;
1412     return false;
1413   }
1414   if (*temperature_celsius < OTC_TEMP_MIN_CELSIUS) {
1415     *temperature_celsius = OTC_TEMP_MIN_CELSIUS;
1416     return false;
1417   }
1418   return true;
1419 }
1420 
isValidOtcLinearModel(const struct OverTempCal * over_temp_cal,float temp_sensitivity,float sensor_intercept)1421 bool isValidOtcLinearModel(const struct OverTempCal *over_temp_cal,
1422                            float temp_sensitivity, float sensor_intercept) {
1423   ASSERT_NOT_NULL(over_temp_cal);
1424 
1425   // Simple check to ensure that the linear model parameters are:
1426   //   1. Within the valid range, AND
1427   //   2. At least one model parameter is considered non-zero.
1428   return NANO_ABS(temp_sensitivity) < over_temp_cal->temp_sensitivity_limit &&
1429          NANO_ABS(sensor_intercept) < over_temp_cal->sensor_intercept_limit &&
1430          (NANO_ABS(temp_sensitivity) > OTC_MODELDATA_NEAR_ZERO_TOL ||
1431          NANO_ABS(sensor_intercept) > OTC_MODELDATA_NEAR_ZERO_TOL);
1432 }
1433 
isValidOtcOffset(const float * offset,float offset_temp_celsius)1434 bool isValidOtcOffset(const float *offset, float offset_temp_celsius) {
1435   ASSERT_NOT_NULL(offset);
1436 
1437   // Simple check to ensure that:
1438   //   1. All of the input data is non "zero".
1439   //   2. The offset temperature is within the valid range.
1440   if (NANO_ABS(offset[0]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
1441       NANO_ABS(offset[1]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
1442       NANO_ABS(offset[2]) < OTC_MODELDATA_NEAR_ZERO_TOL &&
1443       NANO_ABS(offset_temp_celsius) < OTC_MODELDATA_NEAR_ZERO_TOL) {
1444     return false;
1445   }
1446 
1447   // Only returns the "check" result. Don't care about coercion.
1448   return checkAndEnforceTemperatureRange(&offset_temp_celsius);
1449 }
1450 
evaluateWeightingFunction(const struct OverTempCal * over_temp_cal,uint64_t offset_age_nanos)1451 float evaluateWeightingFunction(const struct OverTempCal *over_temp_cal,
1452                                 uint64_t offset_age_nanos) {
1453   ASSERT_NOT_NULL(over_temp_cal);
1454   for (size_t i = 0; i < OTC_NUM_WEIGHT_LEVELS; i++) {
1455     if (offset_age_nanos <=
1456         over_temp_cal->weighting_function[i].offset_age_nanos) {
1457       return over_temp_cal->weighting_function[i].weight;
1458     }
1459   }
1460 
1461   // Returning the default weight for all older offsets.
1462   return OTC_MIN_WEIGHT_VALUE;
1463 }
1464 
modelDataSetAgeUpdate(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1465 void modelDataSetAgeUpdate(struct OverTempCal *over_temp_cal,
1466                            uint64_t timestamp_nanos) {
1467   ASSERT_NOT_NULL(over_temp_cal);
1468   if (over_temp_cal->last_age_update_nanos >= timestamp_nanos) {
1469     // Age updates must be monotonic.
1470     return;
1471   }
1472 
1473   uint64_t age_increment_nanos =
1474       timestamp_nanos - over_temp_cal->last_age_update_nanos;
1475 
1476   // Resets the age update counter.
1477   over_temp_cal->last_age_update_nanos = timestamp_nanos;
1478 
1479   // Updates the model dataset ages.
1480   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
1481     over_temp_cal->model_data[i].offset_age_nanos += age_increment_nanos;
1482   }
1483 }
1484 
1485 /////// DEBUG FUNCTION DEFINITIONS ////////////////////////////////////////////
1486 
1487 #ifdef OVERTEMPCAL_DBG_ENABLED
createDebugTag(struct OverTempCal * over_temp_cal,const char * new_debug_tag)1488 void createDebugTag(struct OverTempCal *over_temp_cal,
1489                     const char *new_debug_tag) {
1490   over_temp_cal->otc_debug_tag[0] = '[';
1491   memcpy(over_temp_cal->otc_debug_tag + 1, over_temp_cal->otc_sensor_tag,
1492          strlen(over_temp_cal->otc_sensor_tag));
1493   memcpy(
1494       over_temp_cal->otc_debug_tag + strlen(over_temp_cal->otc_sensor_tag) + 1,
1495       new_debug_tag, strlen(new_debug_tag) + 1);
1496 }
1497 
updateDebugData(struct OverTempCal * over_temp_cal)1498 void updateDebugData(struct OverTempCal *over_temp_cal) {
1499   ASSERT_NOT_NULL(over_temp_cal);
1500 
1501   // Only update this data if debug printing is not currently in progress
1502   // (i.e., don't want to risk overwriting debug information that is actively
1503   // being reported).
1504   if (over_temp_cal->debug_state != OTC_IDLE) {
1505     return;
1506   }
1507 
1508   // Triggers a debug log printout.
1509   over_temp_cal->debug_print_trigger = true;
1510 
1511   // Initializes the debug data structure.
1512   memset(&over_temp_cal->debug_overtempcal, 0, sizeof(struct DebugOverTempCal));
1513 
1514   // Copies over the relevant data.
1515   for (size_t i = 0; i < 3; i++) {
1516     if (isValidOtcLinearModel(over_temp_cal, over_temp_cal->temp_sensitivity[i],
1517                               over_temp_cal->sensor_intercept[i])) {
1518       over_temp_cal->debug_overtempcal.temp_sensitivity[i] =
1519           over_temp_cal->temp_sensitivity[i];
1520       over_temp_cal->debug_overtempcal.sensor_intercept[i] =
1521           over_temp_cal->sensor_intercept[i];
1522     } else {
1523       // If the model is not valid then just set the debug information so that
1524       // zeros are printed.
1525       over_temp_cal->debug_overtempcal.temp_sensitivity[i] = 0.0f;
1526       over_temp_cal->debug_overtempcal.sensor_intercept[i] = 0.0f;
1527     }
1528   }
1529 
1530   // If 'latest_offset' is defined the copy the data for debug printing.
1531   // Otherwise, the current compensated offset will be printed.
1532   if (over_temp_cal->latest_offset != NULL) {
1533     memcpy(&over_temp_cal->debug_overtempcal.latest_offset,
1534            over_temp_cal->latest_offset, sizeof(struct OverTempModelThreeAxis));
1535   } else {
1536     memcpy(&over_temp_cal->debug_overtempcal.latest_offset,
1537            &over_temp_cal->compensated_offset,
1538            sizeof(struct OverTempModelThreeAxis));
1539   }
1540 
1541   // Total number of OTC model data points.
1542   over_temp_cal->debug_overtempcal.num_model_pts = over_temp_cal->num_model_pts;
1543 
1544   // Computes the maximum error over all of the model data.
1545   overTempGetModelError(over_temp_cal,
1546                         over_temp_cal->debug_overtempcal.temp_sensitivity,
1547                         over_temp_cal->debug_overtempcal.sensor_intercept,
1548                         over_temp_cal->debug_overtempcal.max_error);
1549 }
1550 
overTempCalDebugPrint(struct OverTempCal * over_temp_cal,uint64_t timestamp_nanos)1551 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
1552                            uint64_t timestamp_nanos) {
1553   ASSERT_NOT_NULL(over_temp_cal);
1554 
1555   // This is a state machine that controls the reporting out of debug data.
1556   createDebugTag(over_temp_cal, ":REPORT]");
1557   switch (over_temp_cal->debug_state) {
1558     case OTC_IDLE:
1559       // Wait for a trigger and start the debug printout sequence.
1560       if (over_temp_cal->debug_print_trigger) {
1561         CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "");
1562         CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag, "Debug Version: %s",
1563                       OTC_DEBUG_VERSION_STRING);
1564         over_temp_cal->debug_print_trigger = false;  // Resets trigger.
1565         over_temp_cal->debug_state = OTC_PRINT_OFFSET;
1566       } else {
1567         over_temp_cal->debug_state = OTC_IDLE;
1568       }
1569       break;
1570 
1571     case OTC_WAIT_STATE:
1572       // This helps throttle the print statements.
1573       if (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
1574               timestamp_nanos, over_temp_cal->wait_timer_nanos,
1575               OTC_WAIT_TIME_NANOS)) {
1576         over_temp_cal->debug_state = over_temp_cal->next_state;
1577       }
1578       break;
1579 
1580     case OTC_PRINT_OFFSET:
1581       // Prints out the latest offset estimate (input data).
1582       CAL_DEBUG_LOG(
1583           over_temp_cal->otc_debug_tag,
1584           "Cal#|Offset|Temp|Age [%s|C|nsec]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET
1585           ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
1586           over_temp_cal->otc_unit_tag, over_temp_cal->debug_num_estimates,
1587           CAL_ENCODE_FLOAT(
1588               over_temp_cal->debug_overtempcal.latest_offset.offset[0] *
1589                   over_temp_cal->otc_unit_conversion,
1590               3),
1591           CAL_ENCODE_FLOAT(
1592               over_temp_cal->debug_overtempcal.latest_offset.offset[1] *
1593                   over_temp_cal->otc_unit_conversion,
1594               3),
1595           CAL_ENCODE_FLOAT(
1596               over_temp_cal->debug_overtempcal.latest_offset.offset[2] *
1597                   over_temp_cal->otc_unit_conversion,
1598               3),
1599           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.latest_offset
1600                                .offset_temp_celsius,
1601                            3),
1602           over_temp_cal->debug_overtempcal.latest_offset.offset_age_nanos);
1603 
1604       // clang-format off
1605       over_temp_cal->wait_timer_nanos =
1606           timestamp_nanos;                          // Starts the wait timer.
1607       over_temp_cal->next_state =
1608           OTC_PRINT_MODEL_PARAMETERS;               // Sets the next state.
1609       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1610       // clang-format on
1611       break;
1612 
1613     case OTC_PRINT_MODEL_PARAMETERS:
1614       // Prints out the model parameters.
1615       CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
1616                     "Cal#|Sensitivity [%s/C]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET,
1617                     over_temp_cal->otc_unit_tag,
1618                     over_temp_cal->debug_num_estimates,
1619                     CAL_ENCODE_FLOAT(
1620                         over_temp_cal->debug_overtempcal.temp_sensitivity[0] *
1621                             over_temp_cal->otc_unit_conversion,
1622                         3),
1623                     CAL_ENCODE_FLOAT(
1624                         over_temp_cal->debug_overtempcal.temp_sensitivity[1] *
1625                             over_temp_cal->otc_unit_conversion,
1626                         3),
1627                     CAL_ENCODE_FLOAT(
1628                         over_temp_cal->debug_overtempcal.temp_sensitivity[2] *
1629                             over_temp_cal->otc_unit_conversion,
1630                         3));
1631 
1632       CAL_DEBUG_LOG(over_temp_cal->otc_debug_tag,
1633                     "Cal#|Intercept [%s]: %zu, " CAL_FORMAT_3DIGITS_TRIPLET,
1634                     over_temp_cal->otc_unit_tag,
1635                     over_temp_cal->debug_num_estimates,
1636                     CAL_ENCODE_FLOAT(
1637                         over_temp_cal->debug_overtempcal.sensor_intercept[0] *
1638                             over_temp_cal->otc_unit_conversion,
1639                         3),
1640                     CAL_ENCODE_FLOAT(
1641                         over_temp_cal->debug_overtempcal.sensor_intercept[1] *
1642                             over_temp_cal->otc_unit_conversion,
1643                         3),
1644                     CAL_ENCODE_FLOAT(
1645                         over_temp_cal->debug_overtempcal.sensor_intercept[2] *
1646                             over_temp_cal->otc_unit_conversion,
1647                         3));
1648 
1649       over_temp_cal->wait_timer_nanos =
1650           timestamp_nanos;  // Starts the wait timer.
1651       over_temp_cal->next_state =
1652           OTC_PRINT_MODEL_ERROR;                    // Sets the next state.
1653       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1654       break;
1655 
1656     case OTC_PRINT_MODEL_ERROR:
1657       // Computes the maximum error over all of the model data.
1658       CAL_DEBUG_LOG(
1659           over_temp_cal->otc_debug_tag,
1660           "Cal#|#Updates|#ModelPts|Model Error [%s]: %zu, "
1661           "%zu, %zu, " CAL_FORMAT_3DIGITS_TRIPLET,
1662           over_temp_cal->otc_unit_tag, over_temp_cal->debug_num_estimates,
1663           over_temp_cal->debug_num_model_updates,
1664           over_temp_cal->debug_overtempcal.num_model_pts,
1665           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[0] *
1666                                over_temp_cal->otc_unit_conversion,
1667                            3),
1668           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[1] *
1669                                over_temp_cal->otc_unit_conversion,
1670                            3),
1671           CAL_ENCODE_FLOAT(over_temp_cal->debug_overtempcal.max_error[2] *
1672                                over_temp_cal->otc_unit_conversion,
1673                            3));
1674 
1675       over_temp_cal->model_counter = 0;  // Resets the model data print counter.
1676       over_temp_cal->wait_timer_nanos =
1677           timestamp_nanos;  // Starts the wait timer.
1678       over_temp_cal->next_state = OTC_PRINT_MODEL_DATA;  // Sets the next state.
1679       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1680       break;
1681 
1682     case OTC_PRINT_MODEL_DATA:
1683       // Prints out all of the model data.
1684       if (over_temp_cal->model_counter < over_temp_cal->num_model_pts) {
1685         CAL_DEBUG_LOG(
1686             over_temp_cal->otc_debug_tag,
1687             "  Model[%zu] [%s|C|nsec] = " CAL_FORMAT_3DIGITS_TRIPLET
1688             ", " CAL_FORMAT_3DIGITS ", %" PRIu64,
1689             over_temp_cal->model_counter, over_temp_cal->otc_unit_tag,
1690             CAL_ENCODE_FLOAT(
1691                 over_temp_cal->model_data[over_temp_cal->model_counter]
1692                         .offset[0] *
1693                     over_temp_cal->otc_unit_conversion,
1694                 3),
1695             CAL_ENCODE_FLOAT(
1696                 over_temp_cal->model_data[over_temp_cal->model_counter]
1697                         .offset[1] *
1698                     over_temp_cal->otc_unit_conversion,
1699                 3),
1700             CAL_ENCODE_FLOAT(
1701                 over_temp_cal->model_data[over_temp_cal->model_counter]
1702                         .offset[2] *
1703                     over_temp_cal->otc_unit_conversion,
1704                 3),
1705             CAL_ENCODE_FLOAT(
1706                 over_temp_cal->model_data[over_temp_cal->model_counter]
1707                     .offset_temp_celsius,
1708                 3),
1709             over_temp_cal->model_data[over_temp_cal->model_counter]
1710                 .offset_age_nanos);
1711 
1712         over_temp_cal->model_counter++;
1713         over_temp_cal->wait_timer_nanos =
1714             timestamp_nanos;  // Starts the wait timer.
1715         over_temp_cal->next_state =
1716             OTC_PRINT_MODEL_DATA;  // Sets the next state.
1717         over_temp_cal->debug_state =
1718             OTC_WAIT_STATE;  // First, go to wait state.
1719       } else {
1720         // Sends this state machine to its idle state.
1721         over_temp_cal->wait_timer_nanos =
1722             timestamp_nanos;                   // Starts the wait timer.
1723         over_temp_cal->next_state = OTC_IDLE;  // Sets the next state.
1724         over_temp_cal->debug_state =
1725             OTC_WAIT_STATE;  // First, go to wait state.
1726       }
1727       break;
1728 
1729     default:
1730       // Sends this state machine to its idle state.
1731       over_temp_cal->wait_timer_nanos =
1732           timestamp_nanos;                          // Starts the wait timer.
1733       over_temp_cal->next_state = OTC_IDLE;         // Sets the next state.
1734       over_temp_cal->debug_state = OTC_WAIT_STATE;  // First, go to wait state.
1735   }
1736 }
1737 
overTempCalDebugDescriptors(struct OverTempCal * over_temp_cal,const char * otc_sensor_tag,const char * otc_unit_tag,float otc_unit_conversion)1738 void overTempCalDebugDescriptors(struct OverTempCal *over_temp_cal,
1739                                  const char *otc_sensor_tag,
1740                                  const char *otc_unit_tag,
1741                                  float otc_unit_conversion) {
1742   ASSERT_NOT_NULL(over_temp_cal);
1743   ASSERT_NOT_NULL(otc_sensor_tag);
1744   ASSERT_NOT_NULL(otc_unit_tag);
1745 
1746   // Sets the sensor descriptor, displayed units, and unit conversion factor.
1747   strcpy(over_temp_cal->otc_sensor_tag, otc_sensor_tag);
1748   strcpy(over_temp_cal->otc_unit_tag, otc_unit_tag);
1749   over_temp_cal->otc_unit_conversion = otc_unit_conversion;
1750 }
1751 
overTempGetModelError(const struct OverTempCal * over_temp_cal,const float * temp_sensitivity,const float * sensor_intercept,float * max_error)1752 void overTempGetModelError(const struct OverTempCal *over_temp_cal,
1753                            const float *temp_sensitivity,
1754                            const float *sensor_intercept, float *max_error) {
1755   ASSERT_NOT_NULL(over_temp_cal);
1756   ASSERT_NOT_NULL(temp_sensitivity);
1757   ASSERT_NOT_NULL(sensor_intercept);
1758   ASSERT_NOT_NULL(max_error);
1759 
1760   float max_error_test;
1761   memset(max_error, 0, 3 * sizeof(float));
1762 
1763   for (size_t i = 0; i < over_temp_cal->num_model_pts; i++) {
1764     for (size_t j = 0; j < 3; j++) {
1765       max_error_test =
1766           NANO_ABS(over_temp_cal->model_data[i].offset[j] -
1767                    (temp_sensitivity[j] *
1768                         over_temp_cal->model_data[i].offset_temp_celsius +
1769                     sensor_intercept[j]));
1770       if (max_error_test > max_error[j]) {
1771         max_error[j] = max_error_test;
1772       }
1773     }
1774   }
1775 }
1776 
1777 #endif  // OVERTEMPCAL_DBG_ENABLED
1778