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 "chre/util/nanoapp/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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
316 CHRE_ASSERT_NOT_NULL(offset);
317 CHRE_ASSERT_NOT_NULL(temp_sensitivity);
318 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
419 CHRE_ASSERT_NOT_NULL(offset);
420 CHRE_ASSERT_NOT_NULL(offset_temp_celsius);
421 CHRE_ASSERT_NOT_NULL(timestamp_nanos);
422 CHRE_ASSERT_NOT_NULL(temp_sensitivity);
423 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
440 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
510 CHRE_ASSERT_NOT_NULL(xo);
511 CHRE_ASSERT_NOT_NULL(yo);
512 CHRE_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 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
555 CHRE_ASSERT_NOT_NULL(offset);
556 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
756 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
815 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
834 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
861 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1054 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1081 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1267 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1325 CHRE_ASSERT_NOT_NULL(temp_sensitivity);
1326 CHRE_ASSERT_NOT_NULL(sensor_intercept);
1327 CHRE_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 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1378 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1743 CHRE_ASSERT_NOT_NULL(otc_sensor_tag);
1744 CHRE_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 CHRE_ASSERT_NOT_NULL(over_temp_cal);
1756 CHRE_ASSERT_NOT_NULL(temp_sensitivity);
1757 CHRE_ASSERT_NOT_NULL(sensor_intercept);
1758 CHRE_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