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