From 4fd724822e61d5a80637859fdd7d1ee6b0fa22fe Mon Sep 17 00:00:00 2001 From: zg guo Date: Thu, 16 Feb 2023 21:54:22 -0800 Subject: [PATCH 1/3] add API to access the sensor parameter interfaces of BHY --- Arduino_BHY2/src/BoschSensortec.cpp | 8 ++++++++ Arduino_BHY2/src/BoschSensortec.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/Arduino_BHY2/src/BoschSensortec.cpp b/Arduino_BHY2/src/BoschSensortec.cpp index e9ecdec4..6ff655af 100644 --- a/Arduino_BHY2/src/BoschSensortec.cpp +++ b/Arduino_BHY2/src/BoschSensortec.cpp @@ -131,6 +131,14 @@ void BoschSensortec::getSensorConfiguration(uint8_t id, SensorConfig& virt_senso bhy2_get_virt_sensor_cfg(id, &virt_sensor_conf, &_bhy2); } +int8_t BoschSensortec::bhy2_setParameter(uint16_t param, const uint8_t *buffer, uint32_t length) { + return bhy2_set_parameter(param, buffer, length, &_bhy2); +} + +int8_t BoschSensortec::bhy2_getParameter(uint16_t param, uint8_t *buffer, uint32_t length, uint32_t *actual_len) { + return bhy2_get_parameter(param, buffer, length, actual_len, &_bhy2); +} + uint8_t BoschSensortec::availableSensorData() { return _sensorQueue.size(); diff --git a/Arduino_BHY2/src/BoschSensortec.h b/Arduino_BHY2/src/BoschSensortec.h index 795a1bfb..2e4391a0 100644 --- a/Arduino_BHY2/src/BoschSensortec.h +++ b/Arduino_BHY2/src/BoschSensortec.h @@ -89,6 +89,9 @@ class BoschSensortec { * */ void printSensors(); + int8_t bhy2_setParameter(uint16_t param, const uint8_t *buffer, uint32_t length); + int8_t bhy2_getParameter(uint16_t param, uint8_t *buffer, uint32_t length, uint32_t *actual_len); + /** * @brief Check to see if sensor corresponding to SensorID is present. * From d3d96b327e1c8b9b7d6f0e499ea2658312ed1261 Mon Sep 17 00:00:00 2001 From: zg guo Date: Thu, 16 Feb 2023 22:25:11 -0800 Subject: [PATCH 2/3] add support for BSEC2 gas scanning sensor --- .../BSEC2GasScannerClassify.ino | 43 +++ .../BSEC2GasScannerCollectData.ino | 52 ++++ .../tools/BSEC2DataLogConverter.py | 245 ++++++++++++++++++ Arduino_BHY2/src/Arduino_BHY2.cpp | 1 + Arduino_BHY2/src/Arduino_BHY2.h | 2 + Arduino_BHY2/src/BoschSensortec.cpp | 41 +++ Arduino_BHY2/src/BoschSensortec.h | 7 +- Arduino_BHY2/src/sensors/DataParser.cpp | 22 +- Arduino_BHY2/src/sensors/DataParser.h | 37 +++ Arduino_BHY2/src/sensors/SensorBSEC2.h | 43 +++ .../src/sensors/SensorBSEC2Collector.h | 39 +++ Arduino_BHY2/src/sensors/SensorID.h | 28 +- Arduino_BHY2/src/sensors/SensorTypes.h | 39 ++- 13 files changed, 591 insertions(+), 8 deletions(-) create mode 100644 Arduino_BHY2/examples/BSEC2GasScannerClassify/BSEC2GasScannerClassify.ino create mode 100644 Arduino_BHY2/examples/BSEC2GasScannerCollectData/BSEC2GasScannerCollectData.ino create mode 100644 Arduino_BHY2/examples/BSEC2GasScannerCollectData/tools/BSEC2DataLogConverter.py create mode 100644 Arduino_BHY2/src/sensors/SensorBSEC2.h create mode 100644 Arduino_BHY2/src/sensors/SensorBSEC2Collector.h diff --git a/Arduino_BHY2/examples/BSEC2GasScannerClassify/BSEC2GasScannerClassify.ino b/Arduino_BHY2/examples/BSEC2GasScannerClassify/BSEC2GasScannerClassify.ino new file mode 100644 index 00000000..5df90e91 --- /dev/null +++ b/Arduino_BHY2/examples/BSEC2GasScannerClassify/BSEC2GasScannerClassify.ino @@ -0,0 +1,43 @@ +/* + * This sketch shows how the Nicla board could be used to scan / classify certian gases of interest + * using the on-board BME688 4-in-1 evnironmental sensor cluster + * +*/ + +#include "Arduino.h" +#include "Arduino_BHY2.h" + +SensorBSEC2 bsec2(SENSOR_ID_BSEC2); + +const uint8_t BSEC2CONFIG[] = +//With this example configuration, the BSEC2 library is able to classify 2 types of gases: +//gas 0: ambient regular air in a room +//gas 1: alcohol in a container +//note that the data collected for training the classifying algorithm was rather limited, +//thus the example config string for classifying might not work for your particular settings, +//for optimal results, please collect the data in your settings of interest and generate the config string using the AI studio accordingly +//generally speaking, more data under different scenarios yields better performance + {0,0,2,2,189,1,0,0,0,0,0,0,213,8,0,0,52,0,1,0,0,168,19,73,64,49,119,76,0,192,40,72,0,192,40,72,137,65,0,191,205,204,204,190,0,0,64,191,225,122,148,190,10,0,3,0,216,85,0,100,0,0,96,64,23,183,209,56,28,0,2,0,0,244,1,150,0,50,0,0,128,64,0,0,32,65,144,1,0,0,112,65,0,0,0,63,16,0,3,0,10,215,163,60,10,215,35,59,10,215,35,59,13,0,5,0,0,0,0,0,100,254,131,137,87,88,0,9,0,7,240,150,61,0,0,0,0,0,0,0,0,28,124,225,61,52,128,215,63,0,0,160,64,0,0,0,0,0,0,0,0,205,204,12,62,103,213,39,62,230,63,76,192,0,0,0,0,0,0,0,0,145,237,60,191,251,58,64,63,177,80,131,64,0,0,0,0,0,0,0,0,93,254,227,62,54,60,133,191,0,0,64,64,12,0,10,0,0,0,0,0,0,0,0,0,173,6,11,0,0,0,2,231,201,67,189,125,37,201,61,179,41,106,189,97,167,196,61,84,172,113,62,155,213,214,61,133,10,114,61,62,67,214,61,56,97,57,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,215,83,190,42,215,83,62,0,0,0,0,0,0,0,0,97,101,165,61,88,151,51,190,184,89,165,62,240,207,20,191,47,208,53,63,177,43,63,190,176,56,145,189,228,194,10,191,173,194,44,191,0,0,0,0,146,253,150,61,217,5,157,59,36,134,171,190,159,38,128,59,58,78,29,189,204,88,63,191,210,42,125,190,59,171,228,190,78,165,243,190,0,0,0,0,171,98,187,188,83,234,57,191,66,87,75,62,209,91,130,62,133,244,221,61,242,192,118,190,13,13,52,62,235,86,146,62,147,48,2,191,0,0,0,0,80,192,203,190,252,170,134,189,5,138,208,62,255,220,147,62,184,119,166,62,192,231,125,189,181,36,79,190,124,71,210,62,55,239,13,191,0,0,0,0,226,139,200,189,182,220,91,190,113,205,238,189,235,255,228,190,201,16,66,63,123,50,149,61,80,26,112,62,66,108,128,62,233,205,253,190,0,0,0,0,223,117,24,189,133,115,60,62,197,48,0,189,60,64,194,61,189,86,246,61,185,197,54,189,133,63,90,190,239,233,46,190,14,247,19,191,0,0,0,0,193,26,240,62,151,185,23,190,33,105,234,190,5,24,166,190,197,45,23,63,196,211,145,190,178,103,164,190,125,36,6,191,234,28,114,190,0,0,0,0,136,73,125,62,234,189,27,62,200,69,225,189,15,56,142,190,188,47,134,190,174,248,193,190,221,81,161,190,152,89,51,189,86,157,105,61,0,0,0,0,116,72,209,190,237,104,63,189,60,50,39,189,40,194,15,191,232,34,133,62,163,192,193,61,38,90,147,189,198,159,7,191,240,239,146,61,0,0,0,0,93,146,86,61,185,23,6,62,12,52,10,62,9,82,26,191,186,80,1,63,130,184,195,190,43,204,83,62,73,27,220,189,254,195,200,189,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,63,9,53,212,189,25,224,132,190,0,0,0,0,0,0,0,0,133,45,39,63,65,50,45,191,0,0,0,0,0,0,0,0,76,73,7,62,150,167,209,189,0,0,0,0,0,0,0,0,242,163,107,63,193,223,173,62,0,0,0,0,0,0,0,0,192,205,68,190,213,103,28,63,0,0,0,0,0,0,0,0,60,148,171,62,151,246,154,189,0,0,0,0,0,0,0,0,162,104,218,62,88,44,237,190,0,0,0,0,0,0,0,0,253,226,216,62,249,223,161,189,0,0,0,0,0,0,0,0,168,65,13,190,119,123,179,190,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,160,184,18,72,125,52,223,75,204,85,211,75,119,27,192,75,83,228,150,73,122,154,142,73,133,214,135,73,145,149,237,71,56,196,2,72,221,197,11,72,0,0,0,0,0,0,0,0,0,0,0,0,158,236,10,72,35,30,221,75,206,136,209,75,14,146,190,75,218,105,148,73,65,65,140,73,150,149,133,73,206,248,222,71,219,117,246,71,96,19,4,72,0,0,128,63,0,0,128,63,0,0,128,63,0,0,0,87,1,254,0,2,1,5,48,117,100,0,44,1,112,23,151,7,132,3,197,0,92,4,144,1,64,1,64,1,144,1,48,117,48,117,48,117,48,117,100,0,100,0,100,0,48,117,48,117,48,117,100,0,100,0,48,117,48,117,8,7,8,7,8,7,8,7,8,7,100,0,100,0,100,0,100,0,48,117,48,117,48,117,100,0,100,0,100,0,48,117,48,117,100,0,100,0,255,255,255,255,255,255,255,255,255,255,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,255,255,255,255,255,255,255,255,255,255,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,255,255,255,255,255,255,255,255,255,255,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,112,23,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,220,5,220,5,220,5,255,255,255,255,255,255,220,5,220,5,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,48,117,0,1,0,5,0,2,0,10,0,30,0,5,0,5,0,5,0,5,0,5,0,5,0,64,1,100,0,100,0,100,0,200,0,200,0,200,0,64,1,64,1,64,1,10,0,0,0,0,95,8,0,0 +}; + +void setup() +{ + Serial.begin(115200); + while(!Serial); + + BHY2.begin(); + sensortec.bhy2_bsec2_setConfigString(BSEC2CONFIG, sizeof(BSEC2CONFIG)/sizeof(BSEC2CONFIG[0])); + bsec2.begin(); +} + +void loop() +{ + // Update function should be continuously polled + BHY2.update(100); + + if (bsec2.getNewDataFlag()) { + bsec2.setNewDataFlag(false); + + Serial.println(bsec2.toString()); + } +} diff --git a/Arduino_BHY2/examples/BSEC2GasScannerCollectData/BSEC2GasScannerCollectData.ino b/Arduino_BHY2/examples/BSEC2GasScannerCollectData/BSEC2GasScannerCollectData.ino new file mode 100644 index 00000000..7bf1a9f6 --- /dev/null +++ b/Arduino_BHY2/examples/BSEC2GasScannerCollectData/BSEC2GasScannerCollectData.ino @@ -0,0 +1,52 @@ +/* + * This sketch is used for collecting raw data of BME688, + and the data log after conversion with the helper tools can be used in Bosch Sensortec's AI Studio to train an algorithm + and generate the corresponding config string for the BSEC2 library which can be later used for gas type classification/scanning +*/ + +#include "Arduino.h" +#include "Arduino_BHY2.h" + +SensorBSEC2Collector bsec2Collector(SENSOR_ID_BSEC2_COLLECTOR); + +#define CONFIG_BSEC2_USE_DEAULT_HP 1 + +#if CONFIG_BSEC2_USE_DEAULT_HP +// Default Heater temperature and time base(Recommendation) +const uint16_t BSEC2HP_TEMP[] = {320, 100, 100, 100, 200, 200, 200, 320, 320, 320}; // HP-354 / +const uint16_t BSEC2HP_DUR[] = {5, 2, 10, 30, 5, 5, 5, 5, 5, 5}; // the duration in steps of 140ms, 5 means 700ms, 2 means 280ms +#else +// customized Heater temperature and time base +const uint16_t BSEC2HP_TEMP[] = {100, 320, 320, 200, 200, 200, 320, 320, 320, 320}; // HP-321 / +const uint16_t BSEC2HP_DUR[] = {43, 2, 2, 2, 21, 21, 2, 14, 14, 14}; // the duration in steps of 140ms, 5 means 700ms, 2 means 280ms +#endif + +void setup() +{ + Serial.begin(115200); + while(!Serial); + + BHY2.begin(); + sensortec.bhy2_bsec2_setHP((uint8_t*)BSEC2HP_TEMP, sizeof(BSEC2HP_TEMP), (uint8_t*)BSEC2HP_DUR, sizeof(BSEC2HP_DUR)); + + bsec2Collector.begin(); +} + +void loop() +{ + static auto last_index = 0; + + // Update function should be continuously polled + BHY2.update(); + + if (last_index != bsec2Collector.gas_index()) { + last_index = bsec2Collector.gas_index(); + Serial.println(String((uint32_t)bsec2Collector.timestamp()) + " " + + String(bsec2Collector.temperature()) + " " + + String(bsec2Collector.pressure()) + " " + + String(bsec2Collector.humidity()) + " " + + String(bsec2Collector.gas()) + " " + + String(bsec2Collector.gas_index()) + ); + } +} diff --git a/Arduino_BHY2/examples/BSEC2GasScannerCollectData/tools/BSEC2DataLogConverter.py b/Arduino_BHY2/examples/BSEC2GasScannerCollectData/tools/BSEC2DataLogConverter.py new file mode 100644 index 00000000..5ee802ac --- /dev/null +++ b/Arduino_BHY2/examples/BSEC2GasScannerCollectData/tools/BSEC2DataLogConverter.py @@ -0,0 +1,245 @@ +from datetime import timezone +import datetime +import sys +import os +import json + +# =~=~=~=~=~=~=~=~=~=~=~= PuTTY log 2022.10.21 08:32:17 =~=~=~=~=~=~=~=~=~=~=~= +# 245166 31.27 102027.92 35.10 2049537.13 5 + +# this script converts the data log generated by the Arduino_BHY2/BSEC2GasScanningCollectData.ino example sketch +# to data format acceptable by the BME AI Studio for the training of AI models + +class BSEC2DataLogConverter(): + # sometimes you may want to add an offset to the converted data in situations such as reset of the board, this way + # the converted data contains timestamp that will succeed eariler log files, + # otherwise, there will be overlap of timestamp ranges among the converted data which might confuse the AI studio software + def __init__(self, log_file_name, gas_label, bmeconfig_file, timestamp_offset_ms = 0, dbg=False): + self.log_file_name = log_file_name + self.log_file = open(self.log_file_name, 'r') + self.lines = self.log_file.readlines() + self.log_time_start = None + self.log_timestamp_start = 0 + self.rawdata = [] + self.gas_label = int(gas_label) + self.bmeconfig_file = bmeconfig_file + self.timestamp_offset_ms = timestamp_offset_ms + self.dbg = dbg + if len(self.lines) < 3: + sys.exit("Err: There's too little data in log") + self._clean_data() + self.check_gas_index_missing() + + def _clean_data(self): + # Putty's default log has header which contains the time when the log was created + if "PuTTY" in self.lines[0]: + # 2022.10.21 07:31:17 + self.log_time_start = self.lines[0].split(" ")[3] + " " + self.lines[0].split(" ")[4] + + lines_clean = [] + for line in self.lines[1:]: + if len(line.split(' ')) == 6: + lines_clean.append(line) + #print('line:', '"', line, '"', sep='') + self.lines = lines_clean + + def toString(self): + print(self.lines) + + def check_gas_index_missing(self): + last_index = int(self.lines[0].split(' ')[-1]) + #for i, line in enumerate(self.lines[1:]): + for i, line in enumerate(self.lines[1:-1]): + if self.dbg: + print('dbg:', i, ':"', line, '"', sep='') + cur_index = int(line[-2:-1]) + # print(cur_index) + if (cur_index - last_index != 1) and (cur_index - last_index != -9): + print("Missing gas index at line {}, {} {}".format(i+1, cur_index, last_index)) + last_index = cur_index + + def _get_timestamp_start(self, line): + infos = line.split(' ') + timestamp_ms_start = int(infos[0]) + return timestamp_ms_start + + def unpack_data(self, line): + infos = line.split(' ') + timestamp_ms = int(infos[0]) + self.timestamp_offset_ms + temp = float(infos[1]) + pres = float(infos[2]) + hum = float(infos[3]) + gas = float(infos[4]) + gas_index = int(infos[5]) + return (timestamp_ms, temp, pres, hum, gas, gas_index) + + def format_data(self, Ttphg, time_start_s, label, sensor_index=0, sensor_id=1730555495, scanning_mode_ena=1, error_code=0): + ''' + Ttphg: timestamp_ms, temp, pres, hum, gas, gas_index + ''' + return (sensor_index, sensor_id, Ttphg[0], time_start_s + Ttphg[0]//1000 - self.log_timestamp_start // 1000, + Ttphg[1], Ttphg[2], Ttphg[3], Ttphg[4], Ttphg[5], scanning_mode_ena, label, error_code) + + def parse(self): + if self.log_time_start: + dt = datetime.datetime.strptime(self.log_time_start[2:], "%y.%m.%d %H:%M:%S") + utc_timestamp = int(dt.timestamp()) + else: + dt = datetime.datetime.now(timezone.utc) + utc_time = dt.replace(tzinfo=timezone.utc) + utc_timestamp = int(utc_time.timestamp()) + + self.log_timestamp_start = self._get_timestamp_start(self.lines[0]) + for line in self.lines: + # print(line) + data_unformated = self.unpack_data(line) + data_formated = self.format_data(data_unformated, utc_timestamp, self.gas_label) + # print(data_formated) + self.rawdata.append(data_formated) + + + def _modify_incompatible_keys(self, cfg): + cfg["configHeader"]["dateCreated"] = "" + return cfg + + def _add_raw_data_header(self, cfg): + rawDataHeader = { + "counterPowerOnOff": 1, + "seedPowerOnOff": "", + "counterFileLimit": 1, + "dateCreated": "", + "dateCreated_ISO": "", + "firmwareVersion": "0", + "boardId": "1730555495" + } + cfg["rawDataHeader"] = rawDataHeader + return cfg + + def _add_raw_data_body(self, cfg, data_list): + dataColumns = [ + { + "name": "Sensor Index", + "unit": "", + "format": "integer", + "key": "sensor_index" + }, + { + "name": "Sensor ID", + "unit": "", + "format": "integer", + "key": "sensor_id" + }, + { + "name": "Time Since PowerOn", + "unit": "Milliseconds", + "format": "integer", + "key": "timestamp_since_poweron" + }, + { + "name": "Real time clock", + "unit": "Unix Timestamp: seconds since Jan 01 1970. (UTC); 0 = missing", + "format": "integer", + "key": "real_time_clock" + }, + { + "name": "Temperature", + "unit": "DegreesCelcius", + "format": "float", + "key": "temperature" + }, + { + "name": "Pressure", + "unit": "Hectopascals", + "format": "float", + "key": "pressure" + }, + { + "name": "Relative Humidity", + "unit": "Percent", + "format": "float", + "key": "relative_humidity" + }, + { + "name": "Resistance Gassensor", + "unit": "Ohms", + "format": "float", + "key": "resistance_gassensor" + }, + { + "name": "Heater Profile Step Index", + "unit": "", + "format": "integer", + "key": "heater_profile_step_index" + }, + { + "name": "Scanning Mode Enabled", + "unit": "", + "format": "integer", + "key": "scanning_mode_enabled" + }, + { + "name": "Label Tag", + "unit": "", + "format": "integer", + "key": "label_tag" + }, + { + "name": "Error Code", + "unit": "", + "format": "integer", + "key": "error_code" + } + ] + rawDataBody = { + "dataColumns": dataColumns, + "dataBlock": data_list + } + + cfg["rawDataBody"]= rawDataBody + + return cfg + + def gen_ai_studio_training_data(self): + bme_cfg = json.loads(open(self.bmeconfig_file, "r").read()) + bme_cfg = self._modify_incompatible_keys(bme_cfg) + bme_cfg = self._add_raw_data_header(bme_cfg) + bme_cfg = self._add_raw_data_body(bme_cfg, self.rawdata) + cfg_str = json.dumps(bme_cfg, indent=4) + #filename = self.bmeconfig_file.split(".")[0] + "_gas_" + str(self.gas_label) + ".bmerawdata" + file_name_base = os.path.basename(self.log_file_name) + path_folder = os.path.dirname(self.log_file_name) + filename_wo_ext = os.path.splitext(file_name_base)[0] + + filename = path_folder + '/' + "_gas_" + str(self.gas_label) + '_' + filename_wo_ext + ".bmerawdata" + if self.dbg: + print("dbg: path_folder:", path_folder) + print("dbg: filename_wo_ext:", filename_wo_ext) + print("dbg: filename:", filename) + + bmerawdata_file = open(filename, "w") + bmerawdata_file.write(cfg_str) + print('bmedata: ', '"'+filename+'"', 'generated') + + +if __name__ == '__main__': + """ + Make sure to change gas_label if you collect multiple gas, choose from 0,1,2,3. + """ + if (len(sys.argv) < 4): + print("usage:") + print("\tpython", sys.argv[0], "LOG_FILENAME GAS_LABEL_NUMBER BME_CONFIG_FILE") + print("\t\t GAS_CLASS_NUMBER is a number between 0-3 which is mapped to class A-D in the algorithm settings of BME AI Studio") + print("\t\t BME_CONFIG_FILE is a board config file generated by BME AI Studio Software") + print("example:") + print("\tpython", sys.argv[0], "./datalog/session3-room2/ambient-air.log", "0", "./NiclaSenseME_BoardConfiguration.bmeconfig") + raise BaseException("missing argument") + else: + parser = BSEC2DataLogConverter(log_file_name = sys.argv[1], gas_label = sys.argv[2], bmeconfig_file = sys.argv[3]) + + parser.parse() + """ + Pass the path of the bmeconfig file generated by the BME AI Studio, + and this script will generate 2022_10_21_15_13_BoardConfiguration{gas_label}.bmerawdata for you + """ + parser.gen_ai_studio_training_data() + diff --git a/Arduino_BHY2/src/Arduino_BHY2.cpp b/Arduino_BHY2/src/Arduino_BHY2.cpp index 66b410bc..a3e681f1 100644 --- a/Arduino_BHY2/src/Arduino_BHY2.cpp +++ b/Arduino_BHY2/src/Arduino_BHY2.cpp @@ -62,6 +62,7 @@ bool Arduino_BHY2::begin(NiclaConfig config, NiclaWiring niclaConnection) //in this case, we want to start BLEHandler and DFUManager //so they could come to the rescue the failed firmware for BHI260AP + sensortec.bsecSetBoardTempOffset(0.5f);//assuming the device is powered by USB, if on battery only, use a negative value such as -3.0 if (!(_niclaConfig & NICLA_STANDALONE)) { if (_niclaConfig & NICLA_BLE) { diff --git a/Arduino_BHY2/src/Arduino_BHY2.h b/Arduino_BHY2/src/Arduino_BHY2.h index 1e5a67b0..9f4067fd 100644 --- a/Arduino_BHY2/src/Arduino_BHY2.h +++ b/Arduino_BHY2/src/Arduino_BHY2.h @@ -10,6 +10,8 @@ #include "sensors/SensorXYZ.h" #include "sensors/SensorQuaternion.h" #include "sensors/SensorBSEC.h" +#include "sensors/SensorBSEC2.h" +#include "sensors/SensorBSEC2Collector.h" #include "sensors/SensorActivity.h" #include "sensors/Sensor.h" diff --git a/Arduino_BHY2/src/BoschSensortec.cpp b/Arduino_BHY2/src/BoschSensortec.cpp index 6ff655af..58aa6a8f 100644 --- a/Arduino_BHY2/src/BoschSensortec.cpp +++ b/Arduino_BHY2/src/BoschSensortec.cpp @@ -139,6 +139,47 @@ int8_t BoschSensortec::bhy2_getParameter(uint16_t param, uint8_t *buffer, uint32 return bhy2_get_parameter(param, buffer, length, actual_len, &_bhy2); } +void BoschSensortec::bhy2_bsec2_setConfigString(const uint8_t * buffer, uint32_t length) { + const uint8_t BSEC2_CMD_ENA_WR[] = {0x01,0x00,0x00,0x00}; + const uint8_t BSEC2_CMD_WR_CFG[] = {0x00,0x00,0x00,0x00}; + const uint16_t BSEC2_CFG_PARAM_ID_1 = 0X0802; + const uint16_t BSEC2_CFG_PARAM_ID_2 = 0X0801; + + const uint16_t BLOCK_SIZE = 4; + uint16_t wr_cnt = length / BLOCK_SIZE; + uint8_t remain[BLOCK_SIZE] = {0}; + uint16_t i=0; + + bhy2_setParameter(BSEC2_CFG_PARAM_ID_1, BSEC2_CMD_ENA_WR, sizeof(BSEC2_CMD_ENA_WR)/sizeof(BSEC2_CMD_ENA_WR[0])); + + for (i=0; i>32)) + + String((uint32_t)(timestamp & 0xFFFFFFFF)) + + " temp: " + String(raw_temp, 2) + + " pressure: " + String(raw_pressure, 2) + + " hum: " + String(raw_hum, 2) + + " gas: " + String(raw_gas, 2) + + " gas_index: " + String(gas_index) + + "\n"); + } +}; + class DataParser { public: @@ -77,6 +112,8 @@ class DataParser { static void parseEuler(SensorDataPacket& data, DataOrientation& vector, float scaleFactor); static void parseQuaternion(SensorDataPacket& data, DataQuaternion& vector, float scaleFactor); static void parseBSEC(SensorLongDataPacket& data, DataBSEC& vector); + static void parseBSEC2(SensorDataPacket& data, DataBSEC2& vector); + static void parseBSEC2Collector(SensorLongDataPacket& data, DataBSEC2Collector& vector); static void parseBSECLegacy(SensorLongDataPacket& data, DataBSEC& vector); static void parseData(SensorDataPacket& data, float& value, float scaleFactor, SensorPayload format); static void parseActivity(SensorDataPacket& data, uint16_t& value); diff --git a/Arduino_BHY2/src/sensors/SensorBSEC2.h b/Arduino_BHY2/src/sensors/SensorBSEC2.h new file mode 100644 index 00000000..89c6a533 --- /dev/null +++ b/Arduino_BHY2/src/sensors/SensorBSEC2.h @@ -0,0 +1,43 @@ +#ifndef SENSOR_BSEC2_H_ +#define SENSOR_BSEC2_H_ + +#include "SensorClass.h" + + +class SensorBSEC2 : public SensorClass { +public: + SensorBSEC2() {} + SensorBSEC2(uint8_t id) : SensorClass(id), _data() {} + + bool getNewDataFlag() { return _newDataFlag;} + void setNewDataFlag(bool flag) { _newDataFlag = flag;} + + uint8_t gas_estimates0() {return _data.gas_estimates[0];} + uint8_t gas_estimates1() {return _data.gas_estimates[1];} + uint8_t gas_estimates2() {return _data.gas_estimates[2];} + uint8_t gas_estimates3() {return _data.gas_estimates[3];} + uint8_t accuracy() {return _data.accuracy;} + + + void setData(SensorDataPacket &data) + { + if (_id == SENSOR_ID_BSEC2 ) { + DataParser::parseBSEC2(data, _data); + _newDataFlag = true; + } + } + + void setData(SensorLongDataPacket &data) + { + } + + String toString() + { + return _data.toString(); + } + +private: + DataBSEC2 _data; + bool _newDataFlag; +}; +#endif diff --git a/Arduino_BHY2/src/sensors/SensorBSEC2Collector.h b/Arduino_BHY2/src/sensors/SensorBSEC2Collector.h new file mode 100644 index 00000000..12e5f2e9 --- /dev/null +++ b/Arduino_BHY2/src/sensors/SensorBSEC2Collector.h @@ -0,0 +1,39 @@ +#ifndef SENSOR_BSEC2COLLECTOR_H_ +#define SENSOR_BSEC2COLLECTOR_H_ + +#include "SensorClass.h" + + +class SensorBSEC2Collector : public SensorClass { +public: + SensorBSEC2Collector() {} + SensorBSEC2Collector(uint8_t id) : SensorClass(id), _data() {} + + + uint64_t timestamp() {return _data.timestamp;} + float temperature() {return _data.raw_temp;} + float pressure() {return _data.raw_pressure;} + float humidity() {return _data.raw_hum;} + float gas() {return _data.raw_gas;} + uint8_t gas_index() {return _data.gas_index;} + + void setData(SensorDataPacket &data) + { + } + + void setData(SensorLongDataPacket &data) + { + if (_id == SENSOR_ID_BSEC2_COLLECTOR ) { + DataParser::parseBSEC2Collector(data, _data); + } + } + + String toString() + { + return _data.toString(); + } + +private: + DataBSEC2Collector _data; +}; +#endif diff --git a/Arduino_BHY2/src/sensors/SensorID.h b/Arduino_BHY2/src/sensors/SensorID.h index 5e7b5079..61dc0305 100644 --- a/Arduino_BHY2/src/sensors/SensorID.h +++ b/Arduino_BHY2/src/sensors/SensorID.h @@ -1,8 +1,8 @@ #ifndef SENSOR_ID_H_ #define SENSOR_ID_H_ -#define NUM_SUPPORTEND_SENSOR 79 -#define NUM_LONG_SENSOR 2 +#define NUM_SUPPORTEND_SENSOR 82 +#define NUM_LONG_SENSOR 4 enum SensorID { SENSOR_ID_ACC_PASS = 1, /* Accelerometer passthrough */ @@ -53,7 +53,17 @@ enum SensorID { SENSOR_ID_GYRO_BIAS_WU = 92, /* Gyroscope offset wake up */ SENSOR_ID_MAG_BIAS_WU = 93, /* Magnetometer offset wake up */ SENSOR_ID_STD_WU = 94, /* Step detector wake up */ + SENSOR_ID_KLIO = 112, /* KLIO output */ + SENSOR_ID_PDR = 113, /* PDR output */ + SENSOR_ID_SWIM = 114, /* SWIM output */ SENSOR_ID_BSEC = 115, /* BSEC 1.x output */ + SENSOR_ID_BSEC2_COLLECTOR = 116, /* BSEC 2.x raw data collector for AI training */ + SENSOR_ID_BSEC2 = 117, /* BSEC 2.x gas classifier output */ + SENSOR_ID_HMC = 120, /* HMC output */ + SENSOR_ID_OC = 121, /* OC output */ + SENSOR_ID_NOC = 122, /* NOC output */ + SENSOR_ID_OCE = 123, /* OCE output */ + SENSOR_ID_NOCE = 124, /* NOCE output */ SENSOR_ID_TEMP = 128, /* Temperature */ SENSOR_ID_BARO = 129, /* Barometer */ SENSOR_ID_HUM = 130, /* Humidity */ @@ -101,7 +111,10 @@ enum SensorPayload { PEVENT = 11, ACTIVITY = 12, DEBUG_DATA = 13, - BSEC = 14 + BSEC = 14, + BSEC2 = 15, + BSEC2_COLLECTOR = 16, + KLIO = 17 }; struct SensorStruct @@ -112,9 +125,11 @@ struct SensorStruct }; const -static SensorStruct LongSensorList[2] = { +static SensorStruct LongSensorList[NUM_LONG_SENSOR] = { {SENSOR_ID_BSEC, BSEC, 1.0}, - {SENSOR_ID_BSEC_LEGACY, BSEC, 1.0} + {SENSOR_ID_BSEC2_COLLECTOR, BSEC2_COLLECTOR, 1.0}, + {SENSOR_ID_BSEC_LEGACY, BSEC, 1.0}, + {SENSOR_ID_KLIO, KLIO, 1.0} }; const @@ -167,7 +182,10 @@ static SensorStruct SensorList[NUM_SUPPORTEND_SENSOR] = { {SENSOR_ID_GYRO_BIAS_WU, VECTOR3D, 1.0}, {SENSOR_ID_MAG_BIAS_WU, VECTOR3D, 1.0}, {SENSOR_ID_STD_WU, PEVENT, 1.0}, + {SENSOR_ID_KLIO, KLIO, 1.0}, {SENSOR_ID_BSEC, BSEC, 1.0}, + {SENSOR_ID_BSEC2, BSEC2, 1.0}, + {SENSOR_ID_BSEC2_COLLECTOR, BSEC2_COLLECTOR, 1.0}, {SENSOR_ID_TEMP, P16BITSIGNED, 0.01}, {SENSOR_ID_BARO, P24BITUNSIGNED, 0.0078}, {SENSOR_ID_HUM, P8BITUNISIGNED, 1.0}, diff --git a/Arduino_BHY2/src/sensors/SensorTypes.h b/Arduino_BHY2/src/sensors/SensorTypes.h index 0409adf8..19d6be33 100644 --- a/Arduino_BHY2/src/sensors/SensorTypes.h +++ b/Arduino_BHY2/src/sensors/SensorTypes.h @@ -6,7 +6,10 @@ #define SENSOR_DATA_FIXED_LENGTH (10) -#define SENSOR_LONG_DATA_FIXED_LENGTH (18) +#define SENSOR_LONG_DATA_FIXED_LENGTH (21) + +#define PARAM_SIZE_LENGTH (20) + typedef bhy2_virt_sensor_conf SensorConfig; @@ -96,6 +99,12 @@ struct __attribute__((packed)) SensorDataPacket { return result; } + int32_t getInt24(uint8_t index) { + int32_t result = 0; + result = (int32_t)(getUint24(index) << 8) >> 8; + return result; + } + int32_t getInt32(uint8_t index) { int32_t result = 0; uint8_t length = sizeof(result); @@ -165,6 +174,17 @@ struct __attribute__((packed)) SensorLongDataPacket { return result; } + uint64_t getUint64(uint8_t index) { + uint64_t result = 0; + uint8_t length = sizeof(result); + if (index + length > SENSOR_LONG_DATA_FIXED_LENGTH) { + length = SENSOR_LONG_DATA_FIXED_LENGTH > index ? SENSOR_LONG_DATA_FIXED_LENGTH - index : 0; + } + if (length > 0) + memcpy(&result, &data[index], length); + return result; + } + int8_t getInt8(uint8_t index) { if (index >= SENSOR_LONG_DATA_FIXED_LENGTH) { return 0; @@ -183,6 +203,12 @@ struct __attribute__((packed)) SensorLongDataPacket { return result; } + int32_t getInt24(uint8_t index) { + int32_t result = 0; + result = (int32_t)(getUint24(index) << 8) >> 8; + return result; + } + int32_t getInt32(uint8_t index) { int32_t result = 0; uint8_t length = sizeof(result); @@ -193,6 +219,17 @@ struct __attribute__((packed)) SensorLongDataPacket { memcpy(&result, &data[index], length); return result; } + + int64_t getInt64(uint8_t index) { + int64_t result = 0; + uint8_t length = sizeof(result); + if (index + length > SENSOR_LONG_DATA_FIXED_LENGTH) { + length = SENSOR_LONG_DATA_FIXED_LENGTH > index ? SENSOR_LONG_DATA_FIXED_LENGTH - index : 0; + } + if (length > 0) + memcpy(&result, &data[index], length); + return result; + } }; #endif From 8c6d6a6221e1d9014a475edd9476ef9c74d15b16 Mon Sep 17 00:00:00 2001 From: zg guo Date: Thu, 23 Mar 2023 22:23:30 -0700 Subject: [PATCH 3/3] add the webble support for BSEC2 sensors 116 and 117 --- .../src/static/parse-scheme.json | 30 ++++++++++- .../src/static/sensor-type-map.json | 14 +++++ tools/bhy-controller/src/static/sensor.html | 51 +++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/tools/bhy-controller/src/static/parse-scheme.json b/tools/bhy-controller/src/static/parse-scheme.json index 76f788b6..459cd43e 100644 --- a/tools/bhy-controller/src/static/parse-scheme.json +++ b/tools/bhy-controller/src/static/parse-scheme.json @@ -90,9 +90,35 @@ {"name": "Accuracy", "type": "uint8", "scale-factor": 1}, {"name": "Compensated-Temperature(°C)", "type": "int16", "scale-factor": 0.003906}, {"name": "Compensated-Humidity(%)", "type": "uint16", "scale-factor": 0.002}, - {"name": "Compensated-Gas Resistance(Ohms)", "type": "float", "scale-factor": 1} + {"name": "Compensated-Gas Resistance(Ω)", "type": "uint32", "scale-factor": 1} ] - } + }, + + { + "id": 8, + "type": "BSEC2DataCollectorOutput", + "parse-scheme": + [ + {"name": "Timestamp(ms)", "type": "uint64", "scale-factor": 1}, + {"name": "Temperature(°C)", "type": "int16", "scale-factor": 0.003906}, + {"name": "Pressure(Pa)", "type": "float", "scale-factor": 1}, + {"name": "Humidity(%)", "type": "uint16", "scale-factor": 0.002}, + {"name": "Gas(Ω)", "type": "float", "scale-factor": 1}, + {"name": "Index", "type": "uint8", "scale-factor": 1} + ] + }, + { + "id": 9, + "type": "BSEC2GasClassifierOutput", + "parse-scheme": + [ + {"name": "Gas0(%)", "type": "uint8", "scale-factor": 1}, + {"name": "Gas1(%)", "type": "uint8", "scale-factor": 1}, + {"name": "Gas2(%)", "type": "uint8", "scale-factor": 1}, + {"name": "Gas3(%)", "type": "uint8", "scale-factor": 1}, + {"name": "Accuracy", "type": "uint8", "scale-factor": 1} + ] + } ] } diff --git a/tools/bhy-controller/src/static/sensor-type-map.json b/tools/bhy-controller/src/static/sensor-type-map.json index d14535a6..8ef0a33f 100644 --- a/tools/bhy-controller/src/static/sensor-type-map.json +++ b/tools/bhy-controller/src/static/sensor-type-map.json @@ -353,6 +353,20 @@ "dashboard": 1 }, + "116": { + "name": "BSEC2 Data Collector Output", + "scheme": "BSEC2DataCollectorOutput", + "value": 0, + "dashboard": 1 + }, + + "117": { + "name": "BSEC2 Gas Classifier Output", + "scheme": "BSEC2GasClassifierOutput", + "value": 0, + "dashboard": 1 + }, + "128": { "name": "TEMPERATURE", "scheme": "singleRead", diff --git a/tools/bhy-controller/src/static/sensor.html b/tools/bhy-controller/src/static/sensor.html index 6387e0ae..6af2f3b6 100644 --- a/tools/bhy-controller/src/static/sensor.html +++ b/tools/bhy-controller/src/static/sensor.html @@ -487,6 +487,44 @@

Read sensor data

}); } + } else if (sensorTypes[sensor].scheme == "BSEC2GasClassifierOutput") { + //Parse BSEC values + const BSECValues = parsedStringValue.split(" "); + console.log("Split: ", BSECValues); + + var val0 = BSECValues[1]; //gas 0 probability (%) + var val1 = BSECValues[5]; //gas 1 probability (%) + var val2 = BSECValues[9]; //gas 2 probability (%) + var val3 = BSECValues[13]; //gas 3 probability (%) + //var status = BSECValues[17]; //status + //var total = (val0+val1+val2+val3); + + if (sensorTypes[sensor].value == 0) { //Plot doesn't exist yet because no valid data have been received + Plotly.newPlot(chartIdx, + [ + {y:[val0],name:'Gas0 probability(%)',type:'line'}, {y:[val1],name:'Gas1 probability(%)',type:'line'}, + {y:[val2],name:'Gas2 probability(%)',type:'line'}, {y:[val3],name:'Gas3 probability(%)',type:'line'} + ]); + //Update json to signal that the reception started + cnt = sensorTypes[sensor].value + 1; + sensorTypes[sensor].value = cnt; + } else { //Plot already exists + Plotly.extendTraces(chartIdx,{y:[[val0]]}, [0]); + Plotly.extendTraces(chartIdx,{y:[[val1]]}, [1]); + Plotly.extendTraces(chartIdx,{y:[[val2]]}, [2]); + Plotly.extendTraces(chartIdx,{y:[[val3]]}, [3]); + cnt = sensorTypes[sensor].value; + sensorTypes[sensor].value = cnt + 1; + } + + if (cnt > 150) { + Plotly.relayout(chartIdx,{ + xaxis: { + range: [cnt-150,cnt] + } + }); + } + } } } @@ -524,6 +562,10 @@

Read sensor data

} else { parse_scheme = parseScheme["types"][7]["parse-scheme"]; } + } else if (scheme == "BSEC2DataCollectorOutput") { + parse_scheme = parseScheme["types"][8]["parse-scheme"]; + } else if (scheme == "BSEC2GasClassifierOutput") { + parse_scheme = parseScheme["types"][9]["parse-scheme"]; } parse_scheme.forEach(element => { @@ -547,13 +589,22 @@

Read sensor data

size = 2; } else if (valueType == "uint24") { value = data.getUint16(dataIndex, true) + (data.getUint8(dataIndex+2, true) << 16); + value = value * scale; size = 3; } else if (valueType == "uint32") { value = data.getUint16(dataIndex, true) + (data.getUint16(dataIndex+2, true) << 16); + value = value * scale; size = 4; } else if (valueType == "float") { value = data.getFloat32(dataIndex, true) * scale; size = 4; + } else if (valueType == "uint64") { + valLow = data.getUint32(dataIndex, true) + valHigh = data.getUint32(dataIndex + 4, true) + value = (valLow + (valHigh * 2**32)) * scale + //value = (valLow) * scale + //console.log('value:' + value + 'scale:' + scale) + size = 8; } else if (valueType == "none") { value = eventcount + 1; sensorTypes[sensor].eventcount = value;