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 /*
18  * [OTC Sensor Offset Calibration]
19  * This module implements a runtime algorithm for provisioning over-temperature
20  * compensated (OTC) estimates of a 3-axis sensor's offset (i.e., bias):
21  *
22  *   1) Estimates of sensor offset with associated temperature are consumed,
23  *      {offset, offset_temperature}.
24  *
25  *   2) A linear temperature dependence model is extracted from the collected
26  *      set of data pairs.
27  *
28  *   3) The linear model is used for compensation when no other model points
29  *      (e.g., nearest-temperature, or the latest received offset estimate) can
30  *      be used as a better reference to construct the OTC offset.
31  *
32  *   4) The linear model is used as an extrapolator to provide better
33  *      compensated offset estimates with rapid changes in temperature.
34  *
35  *   5) Other key features of this algorithm:
36  *        a) Jump Detection - The model may contain old data having a variety of
37  *           different thermal histories (hysteresis) which could produce
38  *           discontinuities when using nearest-temperature compensation. If a
39  *           "jump" is detected in comparison to the linear model (or current
40  *           compensation vector, depending on the age of the model), then the
41  *           discontinuity may be minimized by selecting the alternative.
42  *
43  *        b) Outlier Detection - This checks new offset estimates against the
44  *           available linear model. If deviations exceeed a specified limit,
45  *           then the estimate is rejected.
46  *
47  *        c) Model Data Pruning - Old model data that age beyond a specified
48  *           limit is eventually removed from the data set.
49  *
50  *        d) Model Parameter Limits - Bounds on the linear model parameters may
51  *           be specified to qualify acceptable models.
52  *
53  *        e) Offset Update Rate Limits - To minimize computational burden, a
54  *           temporal limit is placed on offset updates prompted from an
55  *           arbitrarily high temperature sampling rate; and a minimum offset
56  *           change is applied to gate small variations in offset during stable
57  *           periods.
58  *
59  *        f) Model-Weighting Based on Age - The least-squares fit uses a
60  *           weighting function based on the age of the model estimate data to
61  *           favor recent estimates and emphasize localized OTC model fitting
62  *           when new updates arrive.
63  *
64  * General Compensation Model Equation:
65  *   sensor_out = sensor_in - compensated_offset
66  *
67  *   When the linear model is used,
68  *     compensated_offset = (temp_sensitivity * current_temp + sensor_intercept)
69  *
70  *   NOTE - 'current_temp' is the current measured temperature.
71  *     'temp_sensitivity' is the modeled temperature sensitivity (i.e., linear
72  *     slope). 'sensor_intercept' is linear model intercept.
73  *
74  *   When the nearest-temperature or latest-offset is used as a "reference",
75  *     delta_temp = current_temp - reference_offset_temperature
76  *     extrapolation_term = temp_sensitivity * delta_temp
77  *     compensated_offset = reference_offset + extrapolation_term
78  *
79  * Assumptions:
80  *   1) Sensor offset temperature dependence is sufficiently "linear".
81  *   2) Impact of sensor hysteresis is small relative to thermal sensitivity.
82  *   3) The impact of long-term offset drift/aging compared to the magnitude of
83  *      deviation resulting from the thermal sensitivity of the offset is
84  *      relatively small.
85  *
86  * Sensor Input and Units:
87  *       - General 3-axis sensor data.
88  *       - Temperature measurements [Celsius].
89  *
90  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
91  *
92  * #define OVERTEMPCAL_DBG_ENABLED to enable debug printout statements.
93  * #define OVERTEMPCAL_DBG_LOG_TEMP to periodically printout sensor temperature.
94  */
95 
96 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_CAL_H_
97 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_CAL_H_
98 
99 #include <stdbool.h>
100 #include <stddef.h>
101 #include <stdint.h>
102 
103 #include "calibration/over_temp/over_temp_model.h"
104 #include "common/math/macros.h"
105 
106 #ifdef __cplusplus
107 extern "C" {
108 #endif
109 
110 // A common sensor operating temperature at which to begin the model jump-start
111 // data.
112 #define JUMPSTART_START_TEMP_CELSIUS (30.0f)
113 
114 // The maximum number of successive outliers that may be rejected.
115 #define OTC_MAX_OUTLIER_COUNT (3)
116 
117 // The 'temp_sensitivity' parameters are set to this value to indicate that the
118 // model is in its initial state.
119 #define OTC_INITIAL_SENSITIVITY (1e6f)
120 
121 // Valid sensor temperature operating range.
122 #define OTC_TEMP_MIN_CELSIUS (-40.0f)
123 #define OTC_TEMP_MAX_CELSIUS (85.0f)
124 
125 // Number of time-interval levels used to define the least-squares weighting
126 // function.
127 #define OTC_NUM_WEIGHT_LEVELS (2)
128 
129 // The time interval used to update the model data age.
130 #define OTC_MODEL_AGE_UPDATE_NANOS (MIN_TO_NANOS(1))
131 
132 // Rate-limits the check of old data to every 2 hours.
133 #define OTC_STALE_CHECK_TIME_NANOS (HRS_TO_NANOS(2))
134 
135 // Time duration in which to enforce using the last offset estimate for
136 // compensation (30 seconds).
137 #define OTC_USE_RECENT_OFFSET_TIME_NANOS (SEC_TO_NANOS(30))
138 
139 // The age at which an offset estimate is considered stale (30 minutes).
140 #define OTC_OFFSET_IS_STALE_NANOS (MIN_TO_NANOS(30))
141 
142 // The refresh interval for the OTC model (30 seconds).
143 #define OTC_REFRESH_MODEL_NANOS (SEC_TO_NANOS(30))
144 
145 // Defines a weighting function value for the linear model fit routine.
146 struct OverTempCalWeight {
147   // The age limit below which an offset will use this weight value.
148   uint64_t offset_age_nanos;
149 
150   // The weighting applied (>0).
151   float weight;
152 };
153 
154 #ifdef OVERTEMPCAL_DBG_ENABLED
155 // Debug printout state enumeration.
156 enum OverTempCalDebugState {
157   OTC_IDLE = 0,
158   OTC_WAIT_STATE,
159   OTC_PRINT_OFFSET,
160   OTC_PRINT_MODEL_PARAMETERS,
161   OTC_PRINT_MODEL_ERROR,
162   OTC_PRINT_MODEL_DATA
163 };
164 
165 // OverTempCal debug information/data tracking structure.
166 struct DebugOverTempCal {
167   // The latest received offset estimate data.
168   struct OverTempModelThreeAxis latest_offset;
169 
170   // The maximum model error over all model_data points.
171   float max_error[3];
172 
173   float temp_sensitivity[3];
174   float sensor_intercept[3];
175   size_t num_model_pts;
176 };
177 #endif  // OVERTEMPCAL_DBG_ENABLED
178 
179 // OverTempCal algorithm parameters (see OverTempCal for details).
180 struct OverTempCalParameters {
181   uint64_t min_temp_update_period_nanos;
182   uint64_t age_limit_nanos;
183   float delta_temp_per_bin;         // [Celsius/bin]
184   float jump_tolerance;             // [sensor units]
185   float outlier_limit;              // [sensor units/Celsius]
186   float temp_sensitivity_limit;     // [sensor units/Celsius]
187   float sensor_intercept_limit;     // [sensor units]
188   float significant_offset_change;  // [sensor units]
189   size_t min_num_model_pts;
190   bool over_temp_enable;
191 };
192 
193 // The following data structure contains all of the necessary components for
194 // modeling a sensor's temperature dependency and providing over-temperature
195 // offset corrections.
196 struct OverTempCal {
197   // Storage for over-temperature model data.
198   struct OverTempModelThreeAxis model_data[OTC_MODEL_SIZE];
199 
200   // Implements a weighting function to emphasize fitting a linear model to
201   // younger offset estimates.
202   struct OverTempCalWeight weighting_function[OTC_NUM_WEIGHT_LEVELS];
203 
204   // The active over-temperature compensated offset estimate data. Contains the
205   // current sensor temperature at which offset compensation is performed.
206   struct OverTempModelThreeAxis compensated_offset;
207 
208   // Timer used to limit the rate at which old estimates are removed from
209   // the 'model_data' collection.
210   uint64_t stale_data_timer_nanos;
211 
212   // Duration beyond which data will be removed to avoid corrupting the model
213   // with drift-compromised data.
214   uint64_t age_limit_nanos;
215 
216   // Timestamp of the last OTC offset compensation update.
217   uint64_t last_offset_update_nanos;
218 
219   // Timestamp of the last OTC model update.
220   uint64_t last_model_update_nanos;
221 
222   // Timestamp of the last OTC model dataset age update.
223   uint64_t last_age_update_nanos;
224 
225   // Limit on the minimum interval for offset update calculations resulting from
226   // an arbitrarily high temperature sampling rate.
227   uint64_t min_temp_update_period_nanos;
228 
229   ///// Online Model Identification Parameters ////////////////////////////////
230   //
231   // The rules for determining whether a new model fit is computed and the
232   // resulting fit parameters are accepted are:
233   //    1) A minimum number of data points must have been collected:
234   //          num_model_pts >= min_num_model_pts
235   //       NOTE: Collecting 'num_model_pts' and given that only one point is
236   //       kept per temperature bin (spanning a thermal range specified by
237   //       'delta_temp_per_bin'), implies that model data covers at least,
238   //          model_temp_span >= 'num_model_pts' * delta_temp_per_bin
239   //    2) A new set of model parameters are accepted if:
240   //         i. The model fit parameters must be within certain absolute bounds:
241   //              a. |temp_sensitivity| < temp_sensitivity_limit
242   //              b. |sensor_intercept| < sensor_intercept_limit
243   float temp_sensitivity_limit;  // [sensor units/Celsius]
244   float sensor_intercept_limit;  // [sensor units]
245   size_t min_num_model_pts;
246 
247   // Pointer to the offset estimate closest to the current sensor temperature.
248   struct OverTempModelThreeAxis *nearest_offset;
249 
250   // Pointer to the most recent offset estimate.
251   struct OverTempModelThreeAxis *latest_offset;
252 
253   // Modeled temperature sensitivity, dOffset/dTemp [sensor_units/Celsius].
254   float temp_sensitivity[3];
255 
256   // Sensor model equation intercept [sensor_units].
257   float sensor_intercept[3];
258 
259   // A limit on the error between nearest-temperature estimate and the model fit
260   // above which the model fit is preferred for providing offset compensation
261   // (also applies to checks between the nearest-temperature and the current
262   // compensated estimate).
263   float jump_tolerance;  // [sensor units]
264 
265   // A limit used to reject new offset estimates that deviate from the current
266   // model fit.
267   float outlier_limit;  // [sensor units]
268 
269   // This parameter is used to detect offset changes that require updates to
270   // system calibration and persistent memory storage.
271   float significant_offset_change;  // [sensor units]
272 
273   // Used to track the previous significant change in temperature.
274   float last_temp_check_celsius;
275 
276   // The rules for accepting new offset estimates into the 'model_data'
277   // collection:
278   //    1) The temperature domain is divided into bins each spanning
279   //       'delta_temp_per_bin'.
280   //    2) Find and replace the i'th 'model_data' estimate data if:
281   //          Let, bin_num = floor(current_temp / delta_temp_per_bin)
282   //          temp_lo_check = bin_num * delta_temp_per_bin
283   //          temp_hi_check = (bin_num + 1) * delta_temp_per_bin
284   //          Check condition:
285   //          temp_lo_check <= model_data[i].offset_temp_celsius < temp_hi_check
286   //    3) If nothing was replaced, and the 'model_data' buffer is not full then
287   //       add the sensor offset estimate to the array.
288   //    4) Otherwise (nothing was replaced and buffer is full), replace the
289   //       oldest data with the incoming one.
290   // This approach ensures a uniform spread of collected data, keeps the most
291   // recent estimates in cases where they arrive frequently near a given
292   // temperature, and prevents model oversampling (i.e., dominance of estimates
293   // concentrated at a given set of temperatures).
294   float delta_temp_per_bin;  // [Celsius/bin]
295 
296   // Total number of model data points collected.
297   size_t num_model_pts;
298 
299   // The number of successive outliers rejected in a row. This is used to
300   // prevent the possibility of a bad state where an initial poor model fit
301   // causes good data to be continually rejected.
302   size_t num_outliers;
303 
304   // Flag set by user to control whether over-temp compensation is used.
305   bool over_temp_enable;
306 
307   // True when new compensation model values have been computed; and reset when
308   // overTempCalNewModelUpdateAvailable() is called. This variable indicates
309   // that the following should be stored in persistent system memory:
310   //    1) 'temp_sensitivity' and 'sensor_intercept'.
311   //    2) The 'compensated_offset' offset data.
312   bool new_overtemp_model_available;
313 
314   // True when a new offset estimate has been computed and registers as a
315   // significant change (i.e., any of the axis offsets change by more than
316   // 'significant_offset_change'); and reset when
317   // overTempCalNewOffsetAvailable() is called. This variable indicates that new
318   // offset data should be stored in persistent system memory.
319   bool new_overtemp_offset_available;
320 
321 #ifdef OVERTEMPCAL_DBG_ENABLED
322   struct DebugOverTempCal debug_overtempcal;  // Debug data structure.
323   enum OverTempCalDebugState debug_state;     // Debug printout state machine.
324   enum OverTempCalDebugState next_state;      // Debug state machine next state.
325   uint64_t wait_timer_nanos;                  // Debug message throttle timer.
326 
327 #ifdef OVERTEMPCAL_DBG_LOG_TEMP
328   uint64_t temperature_print_timer;
329 #endif  // OVERTEMPCAL_DBG_LOG_TEMP
330 
331   size_t model_counter;            // Model output print counter.
332   float otc_unit_conversion;       // Unit conversion for debug display.
333   char otc_unit_tag[16];           // Unit descriptor (e.g., "mDPS").
334   char otc_sensor_tag[16];         // OTC sensor descriptor (e.g., "GYRO").
335   char otc_debug_tag[32];          // Temporary string descriptor.
336   size_t debug_num_model_updates;  // Total number of model updates.
337   size_t debug_num_estimates;      // Total number of offset estimates.
338   bool debug_print_trigger;        // Flag used to trigger data printout.
339 #endif                             // OVERTEMPCAL_DBG_ENABLED
340 };
341 
342 /////// FUNCTION PROTOTYPES ///////////////////////////////////////////////////
343 
344 /*
345  * Initializes the over-temp calibration model identification parameters.
346  *
347  * INPUTS:
348  *   over_temp_cal:             Over-temp main data structure.
349  *   parameters:                An algorithm parameters that contains the
350  *                              following initialization variables.
351  * [parameters]:
352  *   min_num_model_pts:         Minimum number of model points per model
353  *                              calculation update.
354  *   min_temp_update_period_nanos: Limits the rate of offset updates due to an
355  *                                 arbitrarily high temperature sampling rate.
356  *   delta_temp_per_bin:        Temperature span that defines the spacing of
357  *                              collected model estimates.
358  *   jump_tolerance:            Tolerance on acceptable jumps in offset updates.
359  *   outlier_limit:             Outlier offset estimate rejection tolerance.
360  *   age_limit_nanos:           Sets the age limit beyond which a offset
361  *                              estimate is removed from 'model_data'.
362  *   temp_sensitivity_limit:    Values that define the upper limits for the
363  *   sensor_intercept_limit:    model parameters. The acceptance of new model
364  *                              parameters must satisfy:
365  *                          i.  |temp_sensitivity| < temp_sensitivity_limit
366  *                          ii. |sensor_intercept| < sensor_intercept_limit
367  *   significant_offset_change  Minimum limit that triggers offset updates.
368  *   over_temp_enable:          Flag that determines whether over-temp sensor
369  *                              offset compensation is applied.
370  */
371 void overTempCalInit(struct OverTempCal *over_temp_cal,
372                      const struct OverTempCalParameters *parameters);
373 
374 /*
375  * Sets the over-temp calibration model parameters.
376  *
377  * INPUTS:
378  *   over_temp_cal:    Over-temp main data structure.
379  *   offset:           Update values for the latest offset estimate (array).
380  *   offset_temp_celsius: Measured temperature for the offset estimate.
381  *   timestamp_nanos:  Timestamp for the offset estimate [nanoseconds].
382  *   temp_sensitivity: Modeled temperature sensitivity (array).
383  *   sensor_intercept: Linear model intercept for the over-temp model (array).
384  *   jump_start_model: When 'true' populates an empty 'model_data' array using
385  *                     valid input model parameters.
386  *
387  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
388  */
389 void overTempCalSetModel(struct OverTempCal *over_temp_cal, const float *offset,
390                          float offset_temp_celsius, uint64_t timestamp_nanos,
391                          const float *temp_sensitivity,
392                          const float *sensor_intercept, bool jump_start_model);
393 
394 /*
395  * Gets the over-temp calibration model parameters.
396  *
397  * INPUTS:
398  *   over_temp_cal:    Over-temp data structure.
399  * OUTPUTS:
400  *   offset:           Offset values for the latest offset estimate (array).
401  *   offset_temp_celsius: Measured temperature for the offset estimate.
402  *   timestamp_nanos:  Timestamp for the offset estimate [nanoseconds].
403  *   temp_sensitivity: Modeled temperature sensitivity (array).
404  *   sensor_intercept: Linear model intercept for the over-temp model (array).
405  *
406  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
407  */
408 void overTempCalGetModel(struct OverTempCal *over_temp_cal, float *offset,
409                          float *offset_temp_celsius, uint64_t *timestamp_nanos,
410                          float *temp_sensitivity, float *sensor_intercept);
411 
412 /*
413  * Sets the over-temp compensation model data set, and computes new model
414  * parameters provided that 'min_num_model_pts' is satisfied.
415  *
416  * INPUTS:
417  *   over_temp_cal:    Over-temp main data structure.
418  *   model_data:       Array of the new model data set.
419  *   data_length:      Number of model data entries in 'model_data'.
420  *   timestamp_nanos:  Timestamp for the model estimates [nanoseconds].
421  *
422  * NOTE: Max array length for 'model_data' is OTC_MODEL_SIZE.
423  */
424 void overTempCalSetModelData(struct OverTempCal *over_temp_cal,
425                              size_t data_length, uint64_t timestamp_nanos,
426                              const struct OverTempModelThreeAxis *model_data);
427 
428 /*
429  * Gets the over-temp compensation model data set.
430  *
431  * INPUTS:
432  *   over_temp_cal:    Over-temp main data structure.
433  * OUTPUTS:
434  *   model_data:       Array containing the model data set.
435  *   data_length:      Number of model data entries in 'model_data'.
436  *
437  * NOTE: Max array length for 'model_data' is OTC_MODEL_SIZE.
438  */
439 void overTempCalGetModelData(struct OverTempCal *over_temp_cal,
440                              size_t *data_length,
441                              struct OverTempModelThreeAxis *model_data);
442 
443 /*
444  * Gets the current over-temp compensated offset estimate data.
445  *
446  * INPUTS:
447  *   over_temp_cal:    Over-temp data structure.
448  * OUTPUTS:
449  *   compensated_offset: Temperature compensated offset estimate array.
450  *   compensated_offset_temperature_celsius: Compensated offset temperature.
451  *
452  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
453  */
454 void overTempCalGetOffset(struct OverTempCal *over_temp_cal,
455                           float *compensated_offset_temperature_celsius,
456                           float *compensated_offset);
457 
458 /*
459  * Removes the over-temp compensated offset from the input sensor data.
460  *
461  * INPUTS:
462  *   over_temp_cal:    Over-temp data structure.
463  *   timestamp_nanos:  Timestamp of the sensor estimate update.
464  *   xi, yi, zi:       3-axis sensor data to be compensated.
465  * OUTPUTS:
466  *   xo, yo, zo:       3-axis sensor data that has been compensated.
467  */
468 void overTempCalRemoveOffset(struct OverTempCal *over_temp_cal,
469                              uint64_t timestamp_nanos, float xi, float yi,
470                              float zi, float *xo, float *yo, float *zo);
471 
472 // Returns true when a new over-temp model update is available; and the
473 // 'new_overtemp_model_available' flag is reset.
474 bool overTempCalNewModelUpdateAvailable(struct OverTempCal *over_temp_cal);
475 
476 // Returns true when a new over-temp over-temperature offset estimate is
477 // available; and the 'new_overtemp_offset_available' flag is reset.
478 bool overTempCalNewOffsetAvailable(struct OverTempCal *over_temp_cal);
479 
480 /*
481  * Updates the sensor's offset estimate and conditionally incorporates it into
482  * the over-temp model data set, 'model_data'. Updates the model dataset age.
483  *
484  * INPUTS:
485  *   over_temp_cal:       Over-temp data structure.
486  *   timestamp_nanos:     Timestamp of the sensor estimate update.
487  *   offset:              3-axis sensor data to be compensated (array).
488  *   temperature_celsius: Measured temperature for the new sensor estimate.
489  *
490  * NOTE: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
491  */
492 void overTempCalUpdateSensorEstimate(struct OverTempCal *over_temp_cal,
493                                      uint64_t timestamp_nanos,
494                                      const float *offset,
495                                      float temperature_celsius);
496 
497 // Updates the temperature at which the offset compensation is performed (i.e.,
498 // the current measured temperature value). This function is provided mainly for
499 // flexibility since temperature updates may come in from a source other than
500 // the sensor itself, and at a different rate. Since this function is
501 // periodically called, it is also used to update the age of the model
502 // estimates.
503 void overTempCalSetTemperature(struct OverTempCal *over_temp_cal,
504                                uint64_t timestamp_nanos,
505                                float temperature_celsius);
506 
507 /*
508  * Defines an element in the weighting function that is used to control the
509  * fitting behavior of the simple linear model regression used in this module.
510  * The total number of weighting levels that define this functionality is set by
511  * 'OTC_NUM_WEIGHT_LEVELS'. The weight values are expected to be greater than
512  * zero. A particular weight is assigned to a given offset estimate when it's
513  * age is less than 'offset_age_nanos'. NOTE: The ordering of the
514  * 'offset_age_nanos' values in the weight function array should be
515  * monotonically increasing from lowest index to highest so that weighting
516  * selection can be conveniently evaluated. A simple compliance check is
517  * applied, and 'true' is returned when the input weight is positive and older
518  * than the 'index-1' weight's age.
519  *
520  * INPUTS:
521  *   over_temp_cal:    Over-temp data structure.
522  *   index:            Weighting function index.
523  *   new_otc_weight:   Pointer to the settings for the new non-zero weighting
524  *                     value and corresponding age limit below which an offset
525  *                     will use the weight.
526  */
527 bool overTempValidateAndSetWeight(
528     struct OverTempCal *over_temp_cal, size_t index,
529     const struct OverTempCalWeight *new_otc_weight);
530 
531 #ifdef OVERTEMPCAL_DBG_ENABLED
532 // This debug printout function assumes the input sensor data is a gyroscope
533 // [rad/sec]. 'timestamp_nanos' is the current system time.
534 void overTempCalDebugPrint(struct OverTempCal *over_temp_cal,
535                            uint64_t timestamp_nanos);
536 
537 /*
538  * Call this after calling 'overTempCalInit' to set the debug sensor descriptor,
539  * displayed units, and the conversion factor from raw sensor units to the
540  * desired display units. Note the maximum string length allocations.
541  *
542  * INPUTS:
543  *   over_temp_cal:       Over-temp data structure.
544  *   otc_sensor_tag:      Sensor descriptor prefixes debug output.
545  *   otc_unit_tag:        Display unit string.
546  *   otc_unit_conversion: Display unit conversion factor from raw sensor units.
547  */
548 void overTempCalDebugDescriptors(struct OverTempCal *over_temp_cal,
549                                  const char *otc_sensor_tag,
550                                  const char *otc_unit_tag,
551                                  float otc_unit_conversion);
552 
553 /*
554  * Computes the maximum absolute error between the 'model_data' estimates and
555  * the estimate determined by the input model parameters.
556  *   max_error (over all i)
557  *     |model_data[i]->offset_xyz -
558  *       getCompensatedOffset(model_data[i]->offset_temp_celsius,
559  *         temp_sensitivity, sensor_intercept)|
560  *
561  * INPUTS:
562  *   over_temp_cal:    Over-temp data structure.
563  *   temp_sensitivity: Model temperature sensitivity to test (array).
564  *   sensor_intercept: Model intercept to test (array).
565  * OUTPUTS:
566  *   max_error:        Maximum absolute error for the candidate model (array).
567  *
568  * NOTE 1: Arrays are all 3-dimensional with indices: 0=x, 1=y, 2=z.
569  * NOTE 2: This function is provided for testing purposes.
570  */
571 void overTempGetModelError(const struct OverTempCal *over_temp_cal,
572                            const float *temp_sensitivity,
573                            const float *sensor_intercept, float *max_error);
574 #endif  // OVERTEMPCAL_DBG_ENABLED
575 
576 #ifdef __cplusplus
577 }
578 #endif
579 
580 #endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_CALIBRATION_OVER_TEMP_OVER_TEMP_CAL_H_
581