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