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/nano_calibration/nano_calibration.h"
18
19 #include <cstdint>
20 #include <cstring>
21
22 #include "common/techeng_log_util.h"
23
24 namespace nano_calibration {
25 namespace {
26
27 // Common log message sensor-specific identifiers.
28 constexpr char kAccelTag[] = {"[NanoSensorCal:ACCEL_MPS2]"};
29 constexpr char kGyroTag[] = {"[NanoSensorCal:GYRO_RPS]"};
30 constexpr char kMagTag[] = {"[NanoSensorCal:MAG_UT]"};
31
32 // Defines a plan for limiting log messages so that upon initialization there
33 // begins a period set by 'duration_of_rapid_messages_min' where log messages
34 // appear at a rate set by 'rapid_message_interval_sec'. Afterwards, log
35 // messages will be produced at a rate determined by
36 // 'slow_message_interval_min'.
37 struct LogMessageRegimen {
38 uint8_t rapid_message_interval_sec; // Assists device verification.
39 uint8_t slow_message_interval_min; // Avoids long-term log spam.
40 uint8_t duration_of_rapid_messages_min;
41 };
42
43 constexpr LogMessageRegimen kGyroscopeMessagePlan = {
44 /*rapid_message_interval_sec*/ 20,
45 /*slow_message_interval_min*/ 5,
46 /*duration_of_rapid_messages_min*/ 3};
47
48 using ::online_calibration::CalibrationDataThreeAxis;
49 using ::online_calibration::CalibrationTypeFlags;
50 using ::online_calibration::SensorData;
51 using ::online_calibration::SensorIndex;
52 using ::online_calibration::SensorType;
53
54 // NanoSensorCal logging macros.
55 #ifndef LOG_TAG
56 #define LOG_TAG "[ImuCal]"
57 #endif
58
59 #ifdef NANO_SENSOR_CAL_DBG_ENABLED
60 #define NANO_CAL_LOGD(tag, format, ...) \
61 TECHENG_LOGD("%s " format, tag, ##__VA_ARGS__)
62 #define NANO_CAL_LOGW(tag, format, ...) \
63 TECHENG_LOGW("%s " format, tag, ##__VA_ARGS__)
64 #define NANO_CAL_LOGE(tag, format, ...) \
65 TECHENG_LOGE("%s " format, tag, ##__VA_ARGS__)
66 #else
67 #define NANO_CAL_LOGD(tag, format, ...) techeng_log_null(format, ##__VA_ARGS__)
68 #define NANO_CAL_LOGW(tag, format, ...) techeng_log_null(format, ##__VA_ARGS__)
69 #define NANO_CAL_LOGE(tag, format, ...) techeng_log_null(format, ##__VA_ARGS__)
70 #endif // NANO_SENSOR_CAL_DBG_ENABLED
71
72 // NOTE: LOGI is defined to ensure calibration updates are always logged for
73 // field diagnosis and verification.
74 #define NANO_CAL_LOGI(tag, format, ...) \
75 TECHENG_LOGI("%s " format, tag, ##__VA_ARGS__)
76
77 } // namespace
78
Initialize(OnlineCalibrationThreeAxis * accel_cal,OnlineCalibrationThreeAxis * gyro_cal,OnlineCalibrationThreeAxis * mag_cal)79 void NanoSensorCal::Initialize(OnlineCalibrationThreeAxis *accel_cal,
80 OnlineCalibrationThreeAxis *gyro_cal,
81 OnlineCalibrationThreeAxis *mag_cal) {
82 // Loads stored calibration data and initializes the calibration algorithms.
83 accel_cal_ = accel_cal;
84 if (accel_cal_ != nullptr) {
85 if (accel_cal_->get_sensor_type() == SensorType::kAccelerometerMps2) {
86 LoadAshCalibration(CHRE_SENSOR_TYPE_ACCELEROMETER, accel_cal_,
87 &accel_cal_update_flags_, kAccelTag);
88 NANO_CAL_LOGI(kAccelTag,
89 "Accelerometer runtime calibration initialized.");
90 } else {
91 accel_cal_ = nullptr;
92 NANO_CAL_LOGE(kAccelTag, "Failed to initialize: wrong sensor type.");
93 }
94 }
95
96 gyro_cal_ = gyro_cal;
97 if (gyro_cal_ != nullptr) {
98 if (gyro_cal_->get_sensor_type() == SensorType::kGyroscopeRps) {
99 LoadAshCalibration(CHRE_SENSOR_TYPE_GYROSCOPE, gyro_cal_,
100 &gyro_cal_update_flags_, kGyroTag);
101 NANO_CAL_LOGI(kGyroTag, "Gyroscope runtime calibration initialized.");
102 } else {
103 gyro_cal_ = nullptr;
104 NANO_CAL_LOGE(kGyroTag, "Failed to initialize: wrong sensor type.");
105 }
106 }
107
108 mag_cal_ = mag_cal;
109 if (mag_cal != nullptr) {
110 if (mag_cal->get_sensor_type() == SensorType::kMagnetometerUt) {
111 LoadAshCalibration(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, mag_cal_,
112 &mag_cal_update_flags_, kMagTag);
113 NANO_CAL_LOGI(kMagTag, "Magnetometer runtime calibration initialized.");
114 } else {
115 mag_cal_ = nullptr;
116 NANO_CAL_LOGE(kMagTag, "Failed to initialize: wrong sensor type.");
117 }
118 }
119
120 // Resets the initialization timestamp. Set below in HandleSensorSamples.
121 initialization_start_time_nanos_ = 0;
122 }
123
HandleSensorSamples(uint16_t event_type,const chreSensorThreeAxisData * event_data)124 void NanoSensorCal::HandleSensorSamples(
125 uint16_t event_type, const chreSensorThreeAxisData *event_data) {
126 // Converts CHRE Event -> SensorData::SensorType.
127 SensorData sample;
128 switch (event_type) {
129 case CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA:
130 sample.type = SensorType::kAccelerometerMps2;
131 break;
132 case CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA:
133 sample.type = SensorType::kGyroscopeRps;
134 break;
135 case CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA:
136 sample.type = SensorType::kMagnetometerUt;
137 break;
138 default:
139 // This sensor type is not used.
140 NANO_CAL_LOGW("[NanoSensorCal]",
141 "Unexpected 3-axis sensor type received.");
142 return;
143 }
144
145 // Sends the sensor payload to the calibration algorithms and checks for
146 // calibration updates.
147 const auto &header = event_data->header;
148 const auto *data = event_data->readings;
149 sample.timestamp_nanos = header.baseTimestamp;
150 for (size_t i = 0; i < header.readingCount; i++) {
151 sample.timestamp_nanos += data[i].timestampDelta;
152 memcpy(sample.data, data[i].v, sizeof(sample.data));
153 ProcessSample(sample);
154 }
155
156 // Starts tracking the time after initialization to help rate limit gyro log
157 // messaging.
158 if (initialization_start_time_nanos_ == 0) {
159 initialization_start_time_nanos_ = header.baseTimestamp;
160 gyro_notification_time_nanos_ = 0;
161 }
162 }
163
HandleTemperatureSamples(uint16_t event_type,const chreSensorFloatData * event_data)164 void NanoSensorCal::HandleTemperatureSamples(
165 uint16_t event_type, const chreSensorFloatData *event_data) {
166 // Computes the mean of the batched temperature samples and delivers it to the
167 // calibration algorithms. Note, the temperature sensor batch size determines
168 // its minimum update interval.
169 if (event_type == CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA &&
170 event_data->header.readingCount > 0) {
171 const auto header = event_data->header;
172 const auto *data = event_data->readings;
173
174 SensorData sample;
175 sample.type = SensorType::kTemperatureCelsius;
176 sample.timestamp_nanos = header.baseTimestamp;
177
178 float accum_temperature_celsius = 0.0f;
179 for (size_t i = 0; i < header.readingCount; i++) {
180 sample.timestamp_nanos += data[i].timestampDelta;
181 accum_temperature_celsius += data[i].value;
182 }
183 sample.data[SensorIndex::kSingleAxis] =
184 accum_temperature_celsius / header.readingCount;
185 ProcessSample(sample);
186 } else {
187 NANO_CAL_LOGW("[NanoSensorCal]",
188 "Unexpected single-axis sensor type received.");
189 }
190 }
191
ProcessSample(const SensorData & sample)192 void NanoSensorCal::ProcessSample(const SensorData &sample) {
193 // Sends a new sensor sample to each active calibration algorithm and sends
194 // out notifications for new calibration updates.
195 if (accel_cal_ != nullptr) {
196 const CalibrationTypeFlags new_cal_flags =
197 accel_cal_->SetMeasurement(sample);
198 if (new_cal_flags != CalibrationTypeFlags::NONE) {
199 accel_cal_update_flags_ |= new_cal_flags;
200 NotifyAshCalibration(CHRE_SENSOR_TYPE_ACCELEROMETER,
201 accel_cal_->GetSensorCalibration(),
202 accel_cal_update_flags_, kAccelTag);
203 PrintCalibration(accel_cal_->GetSensorCalibration(),
204 accel_cal_update_flags_, kAccelTag);
205
206 if (result_callback_ != nullptr) {
207 result_callback_->SetCalibrationEvent(sample.timestamp_nanos,
208 SensorType::kAccelerometerMps2,
209 accel_cal_update_flags_);
210 }
211 }
212 }
213
214 if (gyro_cal_ != nullptr) {
215 const CalibrationTypeFlags new_cal_flags =
216 gyro_cal_->SetMeasurement(sample);
217 if (new_cal_flags != CalibrationTypeFlags::NONE) {
218 gyro_cal_update_flags_ |= new_cal_flags;
219 if (NotifyAshCalibration(CHRE_SENSOR_TYPE_GYROSCOPE,
220 gyro_cal_->GetSensorCalibration(),
221 gyro_cal_update_flags_, kGyroTag)) {
222 const bool print_gyro_log =
223 HandleGyroLogMessage(sample.timestamp_nanos);
224
225 if (result_callback_ != nullptr &&
226 (print_gyro_log ||
227 gyro_cal_update_flags_ != CalibrationTypeFlags::BIAS)) {
228 // Rate-limits OTC gyro telemetry updates since they can happen
229 // frequently with temperature change. However, all GyroCal stillness
230 // and OTC model parameter updates will be recorded.
231 result_callback_->SetCalibrationEvent(sample.timestamp_nanos,
232 SensorType::kGyroscopeRps,
233 gyro_cal_update_flags_);
234 }
235 }
236 }
237 }
238
239 if (mag_cal_ != nullptr) {
240 const CalibrationTypeFlags new_cal_flags = mag_cal_->SetMeasurement(sample);
241 if (new_cal_flags != CalibrationTypeFlags::NONE) {
242 mag_cal_update_flags_ |= new_cal_flags;
243 NotifyAshCalibration(CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD,
244 mag_cal_->GetSensorCalibration(),
245 mag_cal_update_flags_, kMagTag);
246 PrintCalibration(mag_cal_->GetSensorCalibration(), mag_cal_update_flags_,
247 kMagTag);
248
249 if (result_callback_ != nullptr) {
250 result_callback_->SetCalibrationEvent(sample.timestamp_nanos,
251 SensorType::kMagnetometerUt,
252 mag_cal_update_flags_);
253 }
254 }
255 }
256 }
257
NotifyAshCalibration(uint8_t chreSensorType,const CalibrationDataThreeAxis & cal_data,CalibrationTypeFlags flags,const char * sensor_tag)258 bool NanoSensorCal::NotifyAshCalibration(
259 uint8_t chreSensorType, const CalibrationDataThreeAxis &cal_data,
260 CalibrationTypeFlags flags, const char *sensor_tag) {
261 // Updates the sensor offset calibration using the ASH API.
262 ashCalInfo ash_cal_info;
263 memset(&ash_cal_info, 0, sizeof(ashCalInfo));
264 ash_cal_info.compMatrix[0] = 1.0f; // Sets diagonal to unity (scale factor).
265 ash_cal_info.compMatrix[4] = 1.0f;
266 ash_cal_info.compMatrix[8] = 1.0f;
267 memcpy(ash_cal_info.bias, cal_data.offset, sizeof(ash_cal_info.bias));
268
269 // Maps CalibrationQualityLevel to ASH calibration accuracy.
270 switch (cal_data.calibration_quality.level) {
271 case online_calibration::CalibrationQualityLevel::HIGH_QUALITY:
272 ash_cal_info.accuracy = ASH_CAL_ACCURACY_HIGH;
273 break;
274
275 case online_calibration::CalibrationQualityLevel::MEDIUM_QUALITY:
276 ash_cal_info.accuracy = ASH_CAL_ACCURACY_MEDIUM;
277 break;
278
279 case online_calibration::CalibrationQualityLevel::LOW_QUALITY:
280 ash_cal_info.accuracy = ASH_CAL_ACCURACY_LOW;
281 break;
282
283 default:
284 ash_cal_info.accuracy = ASH_CAL_ACCURACY_UNRELIABLE;
285 break;
286 }
287
288 if (!ashSetCalibration(chreSensorType, &ash_cal_info)) {
289 NANO_CAL_LOGE(sensor_tag, "ASH failed to apply calibration update.");
290 return false;
291 }
292
293 // Uses the ASH API to store all calibration parameters relevant to a given
294 // algorithm as indicated by the input calibration type flags.
295 ashCalParams ash_cal_parameters;
296 memset(&ash_cal_parameters, 0, sizeof(ashCalParams));
297 if (flags & CalibrationTypeFlags::BIAS) {
298 ash_cal_parameters.offsetTempCelsius = cal_data.offset_temp_celsius;
299 memcpy(ash_cal_parameters.offset, cal_data.offset,
300 sizeof(ash_cal_parameters.offset));
301 ash_cal_parameters.offsetSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
302 ash_cal_parameters.offsetTempCelsiusSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
303 }
304
305 if (flags & CalibrationTypeFlags::OVER_TEMP) {
306 memcpy(ash_cal_parameters.tempSensitivity, cal_data.temp_sensitivity,
307 sizeof(ash_cal_parameters.tempSensitivity));
308 memcpy(ash_cal_parameters.tempIntercept, cal_data.temp_intercept,
309 sizeof(ash_cal_parameters.tempIntercept));
310 ash_cal_parameters.tempSensitivitySource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
311 ash_cal_parameters.tempInterceptSource = ASH_CAL_PARAMS_SOURCE_RUNTIME;
312 }
313
314 if (!ashSaveCalibrationParams(chreSensorType, &ash_cal_parameters)) {
315 NANO_CAL_LOGE(sensor_tag, "ASH failed to write calibration update.");
316 return false;
317 }
318
319 return true;
320 }
321
LoadAshCalibration(uint8_t chreSensorType,OnlineCalibrationThreeAxis * online_cal,CalibrationTypeFlags * flags,const char * sensor_tag)322 bool NanoSensorCal::LoadAshCalibration(uint8_t chreSensorType,
323 OnlineCalibrationThreeAxis *online_cal,
324 CalibrationTypeFlags *flags,
325 const char *sensor_tag) {
326 ashCalParams recalled_ash_cal_parameters;
327 if (ashLoadCalibrationParams(chreSensorType, ASH_CAL_STORAGE_ASH,
328 &recalled_ash_cal_parameters)) {
329 // Checks whether a valid set of runtime calibration parameters was received
330 // and can be used for initialization.
331 if (DetectRuntimeCalibration(chreSensorType, sensor_tag, flags,
332 &recalled_ash_cal_parameters)) {
333 CalibrationDataThreeAxis cal_data;
334 cal_data.type = online_cal->get_sensor_type();
335 cal_data.cal_update_time_nanos = chreGetTime();
336
337 // Analyzes the calibration flags and sets only the runtime calibration
338 // values that were received.
339 if (*flags & CalibrationTypeFlags::BIAS) {
340 cal_data.offset_temp_celsius =
341 recalled_ash_cal_parameters.offsetTempCelsius;
342 memcpy(cal_data.offset, recalled_ash_cal_parameters.offset,
343 sizeof(cal_data.offset));
344 }
345
346 if (*flags & CalibrationTypeFlags::OVER_TEMP) {
347 memcpy(cal_data.temp_sensitivity,
348 recalled_ash_cal_parameters.tempSensitivity,
349 sizeof(cal_data.temp_sensitivity));
350 memcpy(cal_data.temp_intercept,
351 recalled_ash_cal_parameters.tempIntercept,
352 sizeof(cal_data.temp_intercept));
353 }
354
355 // Sets the algorithm's initial calibration data and notifies ASH to apply
356 // the recalled calibration data.
357 if (online_cal->SetInitialCalibration(cal_data)) {
358 return NotifyAshCalibration(chreSensorType,
359 online_cal->GetSensorCalibration(), *flags,
360 sensor_tag);
361 } else {
362 NANO_CAL_LOGE(sensor_tag,
363 "Calibration data failed to initialize algorithm.");
364 }
365 }
366 } else {
367 // This is not necessarily an error since there may not be any previously
368 // stored runtime calibration data to load yet (e.g., first device boot).
369 NANO_CAL_LOGW(sensor_tag, "ASH did not recall calibration data.");
370 }
371
372 return false;
373 }
374
DetectRuntimeCalibration(uint8_t chreSensorType,const char * sensor_tag,CalibrationTypeFlags * flags,ashCalParams * ash_cal_parameters)375 bool NanoSensorCal::DetectRuntimeCalibration(uint8_t chreSensorType,
376 const char *sensor_tag,
377 CalibrationTypeFlags *flags,
378 ashCalParams *ash_cal_parameters) {
379 // Analyzes calibration source flags to determine whether runtime
380 // calibration values have been loaded and may be used for initialization. A
381 // valid runtime calibration source will include at least an offset.
382 *flags = CalibrationTypeFlags::NONE; // Resets the calibration flags.
383
384 // Uses the ASH calibration source flags to set the appropriate
385 // CalibrationTypeFlags. These will be used to determine which values to copy
386 // from 'ash_cal_parameters' and provide to the calibration algorithms for
387 // initialization.
388 bool runtime_cal_detected = false;
389 if (ash_cal_parameters->offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME &&
390 ash_cal_parameters->offsetTempCelsiusSource ==
391 ASH_CAL_PARAMS_SOURCE_RUNTIME) {
392 runtime_cal_detected = true;
393 *flags = CalibrationTypeFlags::BIAS;
394 }
395
396 if (ash_cal_parameters->tempSensitivitySource ==
397 ASH_CAL_PARAMS_SOURCE_RUNTIME &&
398 ash_cal_parameters->tempInterceptSource ==
399 ASH_CAL_PARAMS_SOURCE_RUNTIME) {
400 *flags |= CalibrationTypeFlags::OVER_TEMP;
401 }
402
403 if (runtime_cal_detected) {
404 // Prints the retrieved runtime calibration data.
405 NANO_CAL_LOGI(sensor_tag, "Runtime calibration data detected.");
406 PrintAshCalParams(*ash_cal_parameters, sensor_tag);
407 } else {
408 // This is a warning (not an error) since the runtime algorithms will
409 // function correctly with no recalled calibration values. They will
410 // eventually trigger and update the system with valid calibration data.
411 NANO_CAL_LOGW(sensor_tag, "No runtime offset calibration data found.");
412 }
413
414 return runtime_cal_detected;
415 }
416
417 // Helper functions for logging calibration information.
PrintAshCalParams(const ashCalParams & cal_params,const char * sensor_tag)418 void NanoSensorCal::PrintAshCalParams(const ashCalParams &cal_params,
419 const char *sensor_tag) {
420 if (cal_params.offsetSource == ASH_CAL_PARAMS_SOURCE_RUNTIME) {
421 NANO_CAL_LOGI(sensor_tag,
422 "Offset | Temperature [C]: %.6f, %.6f, %.6f | %.2f",
423 cal_params.offset[0], cal_params.offset[1],
424 cal_params.offset[2], cal_params.offsetTempCelsius);
425 }
426
427 if (cal_params.tempSensitivitySource == ASH_CAL_PARAMS_SOURCE_RUNTIME) {
428 NANO_CAL_LOGI(sensor_tag, "Temp Sensitivity [units/C]: %.6f, %.6f, %.6f",
429 cal_params.tempSensitivity[0], cal_params.tempSensitivity[1],
430 cal_params.tempSensitivity[2]);
431 }
432
433 if (cal_params.tempInterceptSource == ASH_CAL_PARAMS_SOURCE_RUNTIME) {
434 NANO_CAL_LOGI(sensor_tag, "Temp Intercept [units]: %.6f, %.6f, %.6f",
435 cal_params.tempIntercept[0], cal_params.tempIntercept[1],
436 cal_params.tempIntercept[2]);
437 }
438
439 if (cal_params.scaleFactorSource == ASH_CAL_PARAMS_SOURCE_RUNTIME) {
440 NANO_CAL_LOGI(sensor_tag, "Scale Factor: %.6f, %.6f, %.6f",
441 cal_params.scaleFactor[0], cal_params.scaleFactor[1],
442 cal_params.scaleFactor[2]);
443 }
444
445 if (cal_params.crossAxisSource == ASH_CAL_PARAMS_SOURCE_RUNTIME) {
446 NANO_CAL_LOGI(sensor_tag,
447 "Cross-Axis in [yx, zx, zy] order: %.6f, %.6f, %.6f",
448 cal_params.crossAxis[0], cal_params.crossAxis[1],
449 cal_params.crossAxis[2]);
450 }
451 }
452
PrintCalibration(const CalibrationDataThreeAxis & cal_data,CalibrationTypeFlags flags,const char * sensor_tag)453 void NanoSensorCal::PrintCalibration(const CalibrationDataThreeAxis &cal_data,
454 CalibrationTypeFlags flags,
455 const char *sensor_tag) {
456 if (flags & CalibrationTypeFlags::BIAS) {
457 NANO_CAL_LOGI(sensor_tag,
458 "Offset | Temperature [C]: %.6f, %.6f, %.6f | %.2f",
459 cal_data.offset[0], cal_data.offset[1], cal_data.offset[2],
460 cal_data.offset_temp_celsius);
461 }
462
463 if (flags & CalibrationTypeFlags::OVER_TEMP) {
464 NANO_CAL_LOGI(sensor_tag, "Temp Sensitivity: %.6f, %.6f, %.6f",
465 cal_data.temp_sensitivity[0], cal_data.temp_sensitivity[1],
466 cal_data.temp_sensitivity[2]);
467 NANO_CAL_LOGI(sensor_tag, "Temp Intercept: %.6f, %.6f, %.6f",
468 cal_data.temp_intercept[0], cal_data.temp_intercept[1],
469 cal_data.temp_intercept[2]);
470 }
471 }
472
HandleGyroLogMessage(uint64_t timestamp_nanos)473 bool NanoSensorCal::HandleGyroLogMessage(uint64_t timestamp_nanos) {
474 // Limits the log messaging update rate for the gyro calibrations since
475 // these can occur frequently with rapid temperature changes.
476 const int64_t next_log_interval_nanos =
477 (NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
478 timestamp_nanos, initialization_start_time_nanos_,
479 MIN_TO_NANOS(kGyroscopeMessagePlan.duration_of_rapid_messages_min)))
480 ? MIN_TO_NANOS(kGyroscopeMessagePlan.slow_message_interval_min)
481 : SEC_TO_NANOS(kGyroscopeMessagePlan.rapid_message_interval_sec);
482
483 const bool print_gyro_log = NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(
484 timestamp_nanos, gyro_notification_time_nanos_, next_log_interval_nanos);
485
486 if (print_gyro_log) {
487 gyro_notification_time_nanos_ = timestamp_nanos;
488 PrintCalibration(gyro_cal_->GetSensorCalibration(), gyro_cal_update_flags_,
489 kGyroTag);
490 }
491
492 return print_gyro_log;
493 }
494
495 } // namespace nano_calibration
496