uses BBC micro:bit to measure and display indoor air quality using Bosch BME680 and/or Sensirion SGP30

Dependencies:   microbit

uses Bosch BME680 and/or Sensirion SGP30 sensors to measure indor air quality

sensors should be connected to BBC micro:bit using i2c

commands are received and data is being sent using uBit / nordic radio protocol

display ---

last line always indicates: - first dot: bme680 detected - second dot: sgp30 detected - third dot: sgp 30 setting humidity/temperature - fourth dor: sgp30 measuring - fith dot: bme680 measuring

the detect dots should be in a stable state (not blinking) the measuring dots should be blinking (constant light means: measurement failed)

if only one bme680 is present: - first 3 lines indicate gas resistence (air quality / more dots == worse quality) - fourth line indicates humidity level

if only sgp30 is present: - first two lines indicate SGP30 VOC level - third and fourth line indicate sgp30 CO2 level

if both sensors are present: - first line indicates SGP30 VOC level - second line line indicates sgp30 CO2 level - third line indicates bme680 gas resistence (air quality) - fourth line indicates bme 680 humidity level

buttons - B display state, switches betweeen - full bright - low light - display off

AB reset sgp30 baseline in non volatile storage

data logging -- during measurements the minimum and mximum values for each measured value (temperature, air pressure, humidity,gas resistance, VOC, CO2) are being stored in non volatile storage those (and the last measurement results) are being shown when btn A has been pressed

Committer:
jsa1969
Date:
Fri Jun 03 17:05:56 2022 +0000
Revision:
60:6b21ca38ee7c
Parent:
59:1ca41f08b6a4
cleanup

Who changed what in which revision?

UserRevisionLine numberNew contents of line
jsa1969 46:2fed2865a0f3 1 #include "IaqNonVolatileStore.h"
jsa1969 46:2fed2865a0f3 2
jsa1969 46:2fed2865a0f3 3 #include "MovingAverage.h"
jsa1969 46:2fed2865a0f3 4
jsa1969 46:2fed2865a0f3 5 /* from microbit.storage:
jsa1969 46:2fed2865a0f3 6 Key Value pairs have a fixed length key of 16 bytes, and a fixed length value of 32 bytes.
jsa1969 46:2fed2865a0f3 7 This class only populates a single block (1024 bytes) in its current state,
jsa1969 46:2fed2865a0f3 8 which means that 21 Key Value pairs can be stored.
jsa1969 46:2fed2865a0f3 9 */
jsa1969 46:2fed2865a0f3 10
jsa1969 49:bbb506b58e6e 11 #define GAS_MAX_STORE_KEY "bme_max_res" // 1
jsa1969 49:bbb506b58e6e 12 #define GAS_MIN_STORE_KEY "bme_min_res" // 2
jsa1969 49:bbb506b58e6e 13 #define TEMP_MAX_STORE_KEY "temp_max" // 3
jsa1969 49:bbb506b58e6e 14 #define TEMP_MIN_STORE_KEY "temp_min" // 4
jsa1969 49:bbb506b58e6e 15 #define PRESS_MAX_STORE_KEY "press_max" // 5
jsa1969 49:bbb506b58e6e 16 #define PRESS_MIN_STORE_KEY "press_min" // 6
jsa1969 49:bbb506b58e6e 17 #define HUM_MAX_STORE_KEY "hum_max" // 7
jsa1969 49:bbb506b58e6e 18 #define HUM_MIN_STORE_KEY "hum_min" // 8
jsa1969 49:bbb506b58e6e 19 #define VOC_MAX_STORE_KEY "voc_max" // 9
jsa1969 49:bbb506b58e6e 20 #define CO_MAX_STORE_KEY "co_max" // 10
jsa1969 49:bbb506b58e6e 21 #define SGP30_ECO_BASE_KEY "eco2_base" // 11
jsa1969 49:bbb506b58e6e 22 #define SGP30_VOC_BASE_KEY "tvoc_base" // 12
jsa1969 49:bbb506b58e6e 23
jsa1969 46:2fed2865a0f3 24
jsa1969 46:2fed2865a0f3 25 const int IaqNonVolatileStore::AVERAGE_BUFFER_SIZE = 10;
jsa1969 46:2fed2865a0f3 26
jsa1969 46:2fed2865a0f3 27 IaqNonVolatileStore::IaqNonVolatileStore(MicroBit* uBit) {
jsa1969 46:2fed2865a0f3 28 _uBit = uBit;
jsa1969 49:bbb506b58e6e 29 _maxGas = getStored(GAS_MAX_STORE_KEY, 0);
jsa1969 46:2fed2865a0f3 30 _minGas = getStored(GAS_MIN_STORE_KEY, 0xffffffff);
jsa1969 46:2fed2865a0f3 31
jsa1969 46:2fed2865a0f3 32 _tempMax = getStored16(TEMP_MAX_STORE_KEY, 0xffff);
jsa1969 46:2fed2865a0f3 33 _tempMin = getStored16(TEMP_MIN_STORE_KEY, 0x7fff);
jsa1969 46:2fed2865a0f3 34
jsa1969 46:2fed2865a0f3 35 _pressMax = getStored(PRESS_MAX_STORE_KEY, 0);
jsa1969 46:2fed2865a0f3 36 _pressMin = getStored(PRESS_MIN_STORE_KEY, 0xffffffff);
jsa1969 46:2fed2865a0f3 37
jsa1969 46:2fed2865a0f3 38 _humMax = getStored(HUM_MAX_STORE_KEY, 0);
jsa1969 46:2fed2865a0f3 39 _humMin = getStored(HUM_MIN_STORE_KEY, 0xffffffff);
jsa1969 46:2fed2865a0f3 40
jsa1969 46:2fed2865a0f3 41 _vocMax = getStored(VOC_MAX_STORE_KEY, 0);
jsa1969 46:2fed2865a0f3 42
jsa1969 46:2fed2865a0f3 43 _coMax = getStored(CO_MAX_STORE_KEY, 0);
jsa1969 46:2fed2865a0f3 44
jsa1969 46:2fed2865a0f3 45 _movingAverage = new MovingAverage(AVERAGE_BUFFER_SIZE);
jsa1969 46:2fed2865a0f3 46 }
jsa1969 46:2fed2865a0f3 47
jsa1969 46:2fed2865a0f3 48 IaqNonVolatileStore::~IaqNonVolatileStore() {
jsa1969 46:2fed2865a0f3 49 if (_movingAverage!=NULL) delete _movingAverage;
jsa1969 46:2fed2865a0f3 50 }
jsa1969 46:2fed2865a0f3 51
jsa1969 46:2fed2865a0f3 52 void IaqNonVolatileStore::clear() {
jsa1969 49:bbb506b58e6e 53 _uBit->storage.remove(GAS_MAX_STORE_KEY);
jsa1969 49:bbb506b58e6e 54 _uBit->storage.remove(SGP30_ECO_BASE_KEY);
jsa1969 49:bbb506b58e6e 55 _uBit->storage.remove(SGP30_VOC_BASE_KEY);
jsa1969 49:bbb506b58e6e 56 resetTmpHumCo2();
jsa1969 49:bbb506b58e6e 57 }
jsa1969 49:bbb506b58e6e 58
jsa1969 49:bbb506b58e6e 59 void IaqNonVolatileStore::resetTmpHumCo2() {
jsa1969 46:2fed2865a0f3 60 _uBit->storage.remove(TEMP_MAX_STORE_KEY);
jsa1969 46:2fed2865a0f3 61 _uBit->storage.remove(TEMP_MIN_STORE_KEY);
jsa1969 46:2fed2865a0f3 62 _uBit->storage.remove(HUM_MAX_STORE_KEY);
jsa1969 46:2fed2865a0f3 63 _uBit->storage.remove(HUM_MIN_STORE_KEY);
jsa1969 59:1ca41f08b6a4 64 _uBit->storage.remove(GAS_MIN_STORE_KEY);
jsa1969 59:1ca41f08b6a4 65 _uBit->storage.remove(PRESS_MAX_STORE_KEY);
jsa1969 59:1ca41f08b6a4 66 _uBit->storage.remove(PRESS_MIN_STORE_KEY);
jsa1969 46:2fed2865a0f3 67 _uBit->storage.remove(CO_MAX_STORE_KEY);
jsa1969 59:1ca41f08b6a4 68 _uBit->storage.remove(VOC_MAX_STORE_KEY);
jsa1969 59:1ca41f08b6a4 69
jsa1969 49:bbb506b58e6e 70 _tempMax = 0xffff;
jsa1969 49:bbb506b58e6e 71 _tempMin = 0x7fff;
jsa1969 59:1ca41f08b6a4 72 _minGas = 0xffffffff;
jsa1969 49:bbb506b58e6e 73 _humMax = 0;
jsa1969 49:bbb506b58e6e 74 _humMin = 0xffffffff;
jsa1969 59:1ca41f08b6a4 75 _pressMax = 0;
jsa1969 59:1ca41f08b6a4 76 _pressMin = 0xffffffff;
jsa1969 59:1ca41f08b6a4 77
jsa1969 50:63442fd5e709 78 _coMax = 0;
jsa1969 50:63442fd5e709 79 _vocMax = 0;
jsa1969 46:2fed2865a0f3 80 }
jsa1969 46:2fed2865a0f3 81
jsa1969 49:bbb506b58e6e 82 uint32_t IaqNonVolatileStore::getGasMax() {
jsa1969 49:bbb506b58e6e 83 return _maxGas;
jsa1969 46:2fed2865a0f3 84 }
jsa1969 46:2fed2865a0f3 85
jsa1969 46:2fed2865a0f3 86 uint32_t IaqNonVolatileStore::getGasMin(){
jsa1969 46:2fed2865a0f3 87 return _minGas;
jsa1969 46:2fed2865a0f3 88 }
jsa1969 46:2fed2865a0f3 89
jsa1969 49:bbb506b58e6e 90 void IaqNonVolatileStore::updateGas(uint32_t gas) {
jsa1969 49:bbb506b58e6e 91 update(gas, &_maxGas, &_minGas, GAS_MAX_STORE_KEY, GAS_MIN_STORE_KEY);
jsa1969 46:2fed2865a0f3 92 }
jsa1969 46:2fed2865a0f3 93
jsa1969 49:bbb506b58e6e 94 void IaqNonVolatileStore::updatePress(const uint32_t pressure) {
jsa1969 49:bbb506b58e6e 95 update(pressure, &_pressMax, &_pressMin, PRESS_MAX_STORE_KEY, PRESS_MIN_STORE_KEY);
jsa1969 49:bbb506b58e6e 96 }
jsa1969 49:bbb506b58e6e 97 void IaqNonVolatileStore::updateHumidity(const uint32_t humidity) {
jsa1969 49:bbb506b58e6e 98 update(humidity, &_humMax, &_humMin, HUM_MAX_STORE_KEY, HUM_MIN_STORE_KEY);
jsa1969 46:2fed2865a0f3 99 }
jsa1969 46:2fed2865a0f3 100
jsa1969 46:2fed2865a0f3 101 int16_t IaqNonVolatileStore::getTempMax(){
jsa1969 46:2fed2865a0f3 102 return _tempMax;
jsa1969 46:2fed2865a0f3 103 }
jsa1969 46:2fed2865a0f3 104
jsa1969 46:2fed2865a0f3 105 int16_t IaqNonVolatileStore::getTempMin(){
jsa1969 46:2fed2865a0f3 106 return _tempMin;
jsa1969 46:2fed2865a0f3 107 }
jsa1969 46:2fed2865a0f3 108
jsa1969 49:bbb506b58e6e 109 void IaqNonVolatileStore::storeIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) {
jsa1969 53:d560c097f1b4 110 _uBit->storage.put(SGP30_ECO_BASE_KEY, (uint8_t *)&eco2_base, sizeof(uint16_t));
jsa1969 53:d560c097f1b4 111 _uBit->storage.put(SGP30_VOC_BASE_KEY, (uint8_t *)&tvoc_base, sizeof(uint16_t));
jsa1969 49:bbb506b58e6e 112 }
jsa1969 49:bbb506b58e6e 113
jsa1969 49:bbb506b58e6e 114 bool IaqNonVolatileStore::getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) {
jsa1969 49:bbb506b58e6e 115 bool ok = false;
jsa1969 49:bbb506b58e6e 116 KeyValuePair* storedEcoBase = _uBit->storage.get(SGP30_ECO_BASE_KEY);
jsa1969 49:bbb506b58e6e 117 KeyValuePair* storedVocBase = _uBit->storage.get(SGP30_VOC_BASE_KEY);
jsa1969 49:bbb506b58e6e 118 if (storedEcoBase!=NULL && storedVocBase!=NULL) {
jsa1969 54:7a73502d5308 119 memcpy(eco2_base, storedEcoBase->value, sizeof(uint16_t));
jsa1969 49:bbb506b58e6e 120 delete storedEcoBase;
jsa1969 54:7a73502d5308 121 memcpy(tvoc_base, storedVocBase->value, sizeof(uint16_t));
jsa1969 49:bbb506b58e6e 122 delete storedVocBase;
jsa1969 49:bbb506b58e6e 123 ok = true;
jsa1969 49:bbb506b58e6e 124 }
jsa1969 49:bbb506b58e6e 125 return ok;
jsa1969 49:bbb506b58e6e 126 }
jsa1969 49:bbb506b58e6e 127
jsa1969 49:bbb506b58e6e 128 void IaqNonVolatileStore::clearIQQBaseline(){
jsa1969 49:bbb506b58e6e 129 _uBit->storage.remove(SGP30_ECO_BASE_KEY);
jsa1969 49:bbb506b58e6e 130 _uBit->storage.remove(SGP30_VOC_BASE_KEY);
jsa1969 49:bbb506b58e6e 131 }
jsa1969 49:bbb506b58e6e 132
jsa1969 46:2fed2865a0f3 133 void IaqNonVolatileStore::updateTemp(const int16_t temp) {
jsa1969 46:2fed2865a0f3 134 if (_tempMax < temp){
jsa1969 46:2fed2865a0f3 135 _tempMax = temp;
jsa1969 46:2fed2865a0f3 136 _uBit->storage.put(TEMP_MAX_STORE_KEY, (uint8_t *)_tempMax, sizeof(int16_t));
jsa1969 46:2fed2865a0f3 137 }
jsa1969 46:2fed2865a0f3 138 if (_tempMin > temp) {
jsa1969 46:2fed2865a0f3 139 _tempMin = temp;
jsa1969 46:2fed2865a0f3 140 _uBit->storage.put(TEMP_MIN_STORE_KEY, (uint8_t *)_tempMin, sizeof(int16_t));
jsa1969 46:2fed2865a0f3 141 }
jsa1969 46:2fed2865a0f3 142 }
jsa1969 46:2fed2865a0f3 143
jsa1969 46:2fed2865a0f3 144 uint32_t IaqNonVolatileStore::getPressMax(){
jsa1969 46:2fed2865a0f3 145 return _pressMax;
jsa1969 46:2fed2865a0f3 146 }
jsa1969 46:2fed2865a0f3 147
jsa1969 46:2fed2865a0f3 148 uint32_t IaqNonVolatileStore::getPressMin(){
jsa1969 46:2fed2865a0f3 149 return _pressMin;
jsa1969 46:2fed2865a0f3 150 }
jsa1969 46:2fed2865a0f3 151
jsa1969 46:2fed2865a0f3 152 uint32_t IaqNonVolatileStore::getHumMax(){
jsa1969 46:2fed2865a0f3 153 return _humMax;
jsa1969 46:2fed2865a0f3 154 }
jsa1969 46:2fed2865a0f3 155
jsa1969 46:2fed2865a0f3 156 uint32_t IaqNonVolatileStore::getHumMin(){
jsa1969 46:2fed2865a0f3 157 return _humMin;
jsa1969 46:2fed2865a0f3 158 }
jsa1969 46:2fed2865a0f3 159
jsa1969 46:2fed2865a0f3 160 uint32_t IaqNonVolatileStore::getVocMax(){
jsa1969 46:2fed2865a0f3 161 return _vocMax;
jsa1969 46:2fed2865a0f3 162 }
jsa1969 46:2fed2865a0f3 163
jsa1969 46:2fed2865a0f3 164 void IaqNonVolatileStore::updateVoc(const uint32_t voc) {
jsa1969 46:2fed2865a0f3 165 update(voc, &_vocMax, NULL, VOC_MAX_STORE_KEY, NULL);
jsa1969 46:2fed2865a0f3 166 }
jsa1969 46:2fed2865a0f3 167
jsa1969 46:2fed2865a0f3 168 uint32_t IaqNonVolatileStore::getCoMax(){
jsa1969 46:2fed2865a0f3 169 return _coMax;
jsa1969 46:2fed2865a0f3 170 }
jsa1969 46:2fed2865a0f3 171
jsa1969 46:2fed2865a0f3 172 void IaqNonVolatileStore::updateCo(const uint32_t co) {
jsa1969 46:2fed2865a0f3 173 update(co, &_coMax, NULL, CO_MAX_STORE_KEY, NULL);
jsa1969 46:2fed2865a0f3 174 }
jsa1969 46:2fed2865a0f3 175
jsa1969 46:2fed2865a0f3 176 void IaqNonVolatileStore::update(const uint32_t value, uint32_t* maxVal, uint32_t* minVal, const char* maxKey, const char* minKey) {
jsa1969 46:2fed2865a0f3 177 if (*maxVal < value) {
jsa1969 46:2fed2865a0f3 178 *maxVal = value;
jsa1969 46:2fed2865a0f3 179 _uBit->storage.put(maxKey, (uint8_t *)maxVal, sizeof(uint32_t));
jsa1969 46:2fed2865a0f3 180 }
jsa1969 46:2fed2865a0f3 181 if (minVal!= NULL && *minVal>value) {
jsa1969 46:2fed2865a0f3 182 *minVal = value;
jsa1969 46:2fed2865a0f3 183 _uBit->storage.put(minKey, (uint8_t *)minVal, sizeof(uint32_t));
jsa1969 46:2fed2865a0f3 184 }
jsa1969 46:2fed2865a0f3 185 }
jsa1969 46:2fed2865a0f3 186
jsa1969 46:2fed2865a0f3 187 const uint32_t* IaqNonVolatileStore::debugInfo() {
jsa1969 46:2fed2865a0f3 188 return _movingAverage->debugInfo();
jsa1969 46:2fed2865a0f3 189 }
jsa1969 46:2fed2865a0f3 190
jsa1969 46:2fed2865a0f3 191 bool IaqNonVolatileStore::strayData() {
jsa1969 46:2fed2865a0f3 192 const uint32_t* values = _movingAverage->debugInfo();
jsa1969 46:2fed2865a0f3 193 uint32_t max = values[0];
jsa1969 46:2fed2865a0f3 194 for (int i=0 ; i<AVERAGE_BUFFER_SIZE; ++i) {
jsa1969 46:2fed2865a0f3 195 uint32_t val = values[i];
jsa1969 46:2fed2865a0f3 196 if (val > max) {
jsa1969 46:2fed2865a0f3 197 max = val;
jsa1969 46:2fed2865a0f3 198 }
jsa1969 46:2fed2865a0f3 199 }
jsa1969 46:2fed2865a0f3 200
jsa1969 46:2fed2865a0f3 201 bool stray = false;
jsa1969 46:2fed2865a0f3 202 for (int i=0 ; stray==false && i<AVERAGE_BUFFER_SIZE; ++i) {
jsa1969 46:2fed2865a0f3 203 stray = (max - values[i]) > 100000;
jsa1969 46:2fed2865a0f3 204 }
jsa1969 46:2fed2865a0f3 205 return stray;
jsa1969 46:2fed2865a0f3 206 }
jsa1969 46:2fed2865a0f3 207
jsa1969 46:2fed2865a0f3 208 uint32_t IaqNonVolatileStore::getStored(const char * key, const uint32_t defaultVal){
jsa1969 46:2fed2865a0f3 209 uint32_t value = defaultVal;
jsa1969 46:2fed2865a0f3 210 KeyValuePair* storedVal = _uBit->storage.get(key);
jsa1969 46:2fed2865a0f3 211 if (storedVal!=NULL) {
jsa1969 46:2fed2865a0f3 212 memcpy(&value, storedVal->value, sizeof(uint32_t));
jsa1969 46:2fed2865a0f3 213 delete storedVal;
jsa1969 46:2fed2865a0f3 214 }
jsa1969 46:2fed2865a0f3 215 return value;
jsa1969 46:2fed2865a0f3 216 }
jsa1969 46:2fed2865a0f3 217
jsa1969 46:2fed2865a0f3 218 int16_t IaqNonVolatileStore::getStored16(const char * key, const int16_t defaultVal){
jsa1969 46:2fed2865a0f3 219 int16_t value = defaultVal;
jsa1969 46:2fed2865a0f3 220 KeyValuePair* storedVal = _uBit->storage.get(key);
jsa1969 46:2fed2865a0f3 221 if (storedVal!=NULL) {
jsa1969 46:2fed2865a0f3 222 memcpy(&value, storedVal->value, sizeof(int16_t));
jsa1969 46:2fed2865a0f3 223 delete storedVal;
jsa1969 46:2fed2865a0f3 224 }
jsa1969 46:2fed2865a0f3 225 return value;
jsa1969 46:2fed2865a0f3 226 }