/*! * Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. * * BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @file bme68x_datalogger.cpp * @date 03 Jan 2024 * @version 2.1.5 * * @brief bme68x_datalogger * * */ /* own header include */ #include "bme68x_datalogger.h" #include /*! * @brief The constructor of the bme68xDataLogger class */ bme68xDataLogger::bme68xDataLogger() : _file_counter(1) {} /*! * @brief Function to configure the datalogger using the provided sensor config file */ demo_ret_code bme68xDataLogger::begin(const String& config_name) { demo_ret_code ret_code = utils::begin(); _config_name = config_name; if (ret_code >= EDK_OK) { /* Resets the file counter when seed file is generated */ _file_counter = 1; ret_code = create_log_file(); if (ret_code >= EDK_OK) { ret_code = create_label_info_file(); } _ss.setf(std::ios::fixed, std::ios::floatfield); } return ret_code; } /*! * @brief Function which flushes the buffered sensor data to the current log file */ demo_ret_code bme68xDataLogger::flush() { demo_ret_code ret_code = EDK_OK; File logFile; std::string txt; if (_ss.rdbuf()->in_avail()) { txt = _ss.str(); _ss.str(std::string()); if (_file_counter && logFile.open(_log_file_name.c_str(), O_RDWR | O_AT_END)) { logFile.seek(_sensor_data_pos); logFile.print(txt.c_str()); _sensor_data_pos = logFile.position(); logFile.println("\n\t ]\n\t}\n}"); if (logFile.size() >= FILE_SIZE_LIMIT) { logFile.close(); ++_file_counter; ret_code = create_log_file(); if (ret_code >= EDK_OK) { ret_code = create_label_info_file(); } } else { logFile.close(); } } else { ret_code = EDK_DATALOGGER_LOG_FILE_ERROR; } } return ret_code; } /*! * @brief Function writes the sensor data to the current log file */ demo_ret_code bme68xDataLogger::write_sensor_data(const uint8_t* num, const uint32_t* sensor_id, const uint8_t* sensorMode, const bme68x_data* bme68xData, const uint32_t* scan_cycle_index, gas_label label, demo_ret_code code) { demo_ret_code ret_code = EDK_OK; uint32_t rtc_tsp = utils::get_rtc().now().unixtime(); uint32_t time_since_power_on = millis(); if (_end_of_line) { _ss << ",\n"; } _ss << "\t\t\t[\n\t\t\t\t"; (num != nullptr) ? (_ss << (uint32_t)*num) : (_ss << "null"); _ss << ",\n\t\t\t\t"; (sensor_id != nullptr) ? (_ss << (uint32_t)*sensor_id) : (_ss << "null"); _ss << ",\n\t\t\t\t"; _ss << time_since_power_on; _ss << ",\n\t\t\t\t"; _ss << rtc_tsp; _ss << ",\n\t\t\t\t"; (bme68xData != nullptr) ? (_ss << bme68xData->temperature) : (_ss << "null"); _ss << ",\n\t\t\t\t"; (bme68xData != nullptr) ? (_ss << (bme68xData->pressure * .01f)) : (_ss << "null"); _ss << ",\n\t\t\t\t"; (bme68xData != nullptr) ? (_ss << bme68xData->humidity) : (_ss << "null"); _ss << ",\n\t\t\t\t"; (bme68xData != nullptr) ? (_ss << bme68xData->gas_resistance) : (_ss << "null"); _ss << ",\n\t\t\t\t"; (bme68xData != nullptr) ? (_ss << (uint32_t)bme68xData->gas_index) : (_ss << "null"); _ss << ",\n\t\t\t\t"; _ss << (bool)(BME68X_PARALLEL_MODE); _ss << ",\n\t\t\t\t"; (scan_cycle_index != nullptr) ? (_ss << (uint32_t)(*scan_cycle_index)) : (_ss << "null"); _ss << ",\n\t\t\t\t"; _ss << (uint32_t)label; _ss << ",\n\t\t\t\t"; _ss << (uint32_t)code; _ss << "\n\t\t\t]"; _end_of_line = true; return ret_code; } /*! * @brief Function stores the labelTag, labelName and labelDescription to the .bmelabelinfo file */ demo_ret_code bme68xDataLogger::set_label_info(int32_t label_tag, const String& label_name, const String& label_desc) { demo_ret_code ret_code = EDK_OK; File logFile; if (logFile.open(_label_file_name.c_str(), O_READ)) { DeserializationError error = deserializeJson(label_doc, logFile); logFile.close(); if (error) { Serial.println(error.c_str()); return EDK_SENSOR_MANAGER_JSON_DESERIAL_ERROR; } JsonArray lblInfo = label_doc["labelInformation"].as(); JsonObject obj = lblInfo.createNestedObject(); obj["labelTag"] = label_tag; obj["labelName"] = label_name; obj["labelDescription"] = label_desc; if (logFile.open(_label_file_name.c_str(), O_RDWR | O_TRUNC)) { serializeJsonPretty(label_doc, logFile); logFile.close(); } else { ret_code = EDK_DATALOGGER_LABEL_INFO_FILE_ERROR; } } else { ret_code = EDK_DATALOGGER_LABEL_INFO_FILE_ERROR; } return ret_code; } /*! * @brief Function to create a bme68x datalogger output file with .bmerawdata extension */ demo_ret_code bme68xDataLogger::create_log_file() { demo_ret_code ret_code = EDK_OK; String mac_str = utils::get_mac_address(); String log_file_base_name = "_Board_" + mac_str + "_PowerOnOff_1_"; _log_file_name = utils::get_date_time() + log_file_base_name + utils::get_file_seed() + "_File_" + String(_file_counter) + BME68X_RAWDATA_FILE_EXT; File configFile, logFile; if (_config_name.length() && !configFile.open(_config_name.c_str(), O_RDWR)) { ret_code = EDK_DATALOGGER_SENSOR_CONFIG_FILE_ERROR; } else if (!logFile.open(_log_file_name.c_str(), O_RDWR | O_CREAT)) { ret_code = EDK_DATALOGGER_LOG_FILE_ERROR; } else { if (_config_name.length()) { String line_buffer; /* read in each line from the config file and copy it to the log file */ while (configFile.available()) { line_buffer = configFile.readStringUntil('\n'); /* skip the last closing curly bracket of the JSON document */ if (line_buffer == "}") { logFile.println("\t,"); break; } logFile.println(line_buffer); } configFile.close(); } else { logFile.println("{"); } /* write data header / skeleton */ /* raw data header */ logFile.println("\t\"rawDataHeader\": {"); logFile.println("\t\t\"counterPowerOnOff\": 1,"); logFile.println("\t\t\"seedPowerOnOff\": \"" + utils::get_file_seed() + "\","); logFile.println("\t\t\"counterFileLimit\": " + String(_file_counter) + ","); logFile.println("\t\t\"dateCreated\": \"" + String(utils::get_rtc().now().unixtime()) + "\","); logFile.println("\t\t\"dateCreated_ISO\": \"" + utils::get_rtc().now().timestamp() + "+00:00\","); logFile.println("\t\t\"firmwareVersion\": \"" + String(FIRMWARE_VERSION) + "\","); logFile.println("\t\t\"boardId\": \"" + mac_str + "\""); logFile.println("\t},"); logFile.println("\t\"rawDataBody\": {"); logFile.println("\t\t\"dataColumns\": ["); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Sensor Index\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"sensor_index\","); logFile.println("\t\t\t\t\"colId\": 1"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Sensor ID\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"sensor_id\","); logFile.println("\t\t\t\t\"colId\": 2"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Time Since PowerOn\","); logFile.println("\t\t\t\t\"unit\": \"Milliseconds\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"timestamp_since_poweron\","); logFile.println("\t\t\t\t\"colId\": 3"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Real time clock\","); logFile.println("\t\t\t\t\"unit\": \"Unix Timestamp: seconds since Jan 01 1970. (UTC); 0 = missing\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"real_time_clock\","); logFile.println("\t\t\t\t\"colId\": 4"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Temperature\","); logFile.println("\t\t\t\t\"unit\": \"DegreesCelcius\","); logFile.println("\t\t\t\t\"format\": \"float\","); logFile.println("\t\t\t\t\"key\": \"temperature\","); logFile.println("\t\t\t\t\"colId\": 5"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Pressure\","); logFile.println("\t\t\t\t\"unit\": \"Hectopascals\","); logFile.println("\t\t\t\t\"format\": \"float\","); logFile.println("\t\t\t\t\"key\": \"pressure\","); logFile.println("\t\t\t\t\"colId\": 6"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Relative Humidity\","); logFile.println("\t\t\t\t\"unit\": \"Percent\","); logFile.println("\t\t\t\t\"format\": \"float\","); logFile.println("\t\t\t\t\"key\": \"relative_humidity\","); logFile.println("\t\t\t\t\"colId\": 7"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Resistance Gassensor\","); logFile.println("\t\t\t\t\"unit\": \"Ohms\","); logFile.println("\t\t\t\t\"format\": \"float\","); logFile.println("\t\t\t\t\"key\": \"resistance_gassensor\","); logFile.println("\t\t\t\t\"colId\": 8"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Heater Profile Step Index\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"heater_profile_step_index\","); logFile.println("\t\t\t\t\"colId\": 9"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Scanning Mode Enabled\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"boolean\","); logFile.println("\t\t\t\t\"key\": \"scanning_enabled\","); logFile.println("\t\t\t\t\"colId\": 10"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Scanning Cycle Index\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"scanning_cycle_index\","); logFile.println("\t\t\t\t\"colId\": 11"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Label Tag\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"label_tag\","); logFile.println("\t\t\t\t\"colId\": 12"); logFile.println("\t\t\t},"); logFile.println("\t\t\t{"); logFile.println("\t\t\t\t\"name\": \"Error Code\","); logFile.println("\t\t\t\t\"unit\": \"\","); logFile.println("\t\t\t\t\"format\": \"integer\","); logFile.println("\t\t\t\t\"key\": \"error_code\","); logFile.println("\t\t\t\t\"colId\": 13"); logFile.println("\t\t\t}"); logFile.println("\t\t],"); /* data block */ logFile.println("\t\t\"dataBlock\": ["); /* save position in file, where to write the first data set */ _sensor_data_pos = logFile.position(); logFile.println("\t\t]"); logFile.println("\t}"); logFile.println("}"); /* close log file */ logFile.close(); _end_of_line = false; } return ret_code; } /*! * @brief Function to create a bme68x label information file with .bmelabelinfo extension */ demo_ret_code bme68xDataLogger::create_label_info_file() { demo_ret_code ret_code = EDK_OK; String mac_str = utils::get_mac_address(); String label_file_base_name = "_Board_" + mac_str + "_PowerOnOff_1_"; _label_file_name = utils::get_date_time() + label_file_base_name + utils::get_file_seed() + "_File_" + String(_file_counter) + BME68X_LABEL_INFO_FILE_EXT; File logFile; if (!logFile.open(_label_file_name.c_str(), O_RDWR | O_CREAT)) { ret_code = EDK_DATALOGGER_LABEL_INFO_FILE_ERROR; } else { /* write labelinfo header / skeleton */ logFile.println("{"); logFile.println("\t\"labelInfoHeader\": {"); logFile.println("\t\t\"counterPowerOnOff\": 1,"); logFile.println("\t\t\"seedPowerOnOff\": \"" + utils::get_file_seed() + "\","); logFile.println("\t\t\"dateCreated\": \"" + String(utils::get_rtc().now().unixtime()) + "\","); logFile.println("\t\t\"dateCreated_ISO\": \"" + utils::get_rtc().now().timestamp() + "+00:00\","); logFile.println("\t\t\"firmwareVersion\": \"" + String(FIRMWARE_VERSION) + "\","); logFile.println("\t\t\"boardId\": \"" + mac_str + "\""); logFile.println("\t},"); logFile.println("\t\"labelInformation\": ["); logFile.println("\t\t{"); logFile.println("\t\t\t\"labelTag\": 0,"); logFile.println("\t\t\t\"labelName\": \"Initial\","); logFile.println("\t\t\t\"labelDescription\": \"Standard label for no label has been set\""); logFile.println("\t\t},"); logFile.println("\t\t{"); logFile.println("\t\t\t\"labelTag\": 1,"); logFile.println("\t\t\t\"labelName\": \"Button 1\","); logFile.println("\t\t\t\"labelDescription\": \"Standard label for hardware button 1 pressed\""); logFile.println("\t\t},"); logFile.println("\t\t{"); logFile.println("\t\t\t\"labelTag\": 2,"); logFile.println("\t\t\t\"labelName\": \"Button 2\","); logFile.println("\t\t\t\"labelDescription\": \"Standard label for hardware button 2 pressed\""); logFile.println("\t\t},"); logFile.println("\t\t{"); logFile.println("\t\t\t\"labelTag\": 3,"); logFile.println("\t\t\t\"labelName\": \"Button 1+2\","); logFile.println("\t\t\t\"labelDescription\": \"Standard label for hardware button 1 and button 2 pressed\""); logFile.print("\t\t}"); logFile.println("\n\t]"); logFile.print("}"); /* close log file */ logFile.close(); } return ret_code; }