diff --git a/README.rst b/README.rst index f6ddeb7..6d972f2 100644 --- a/README.rst +++ b/README.rst @@ -74,7 +74,7 @@ Usage Example print("") sleep(1) -For humidity compensated raw gas readings, we'll need a secondary sensor such as the bme280 +For humidity compensated raw gas and voc index readings, we'll need a secondary sensor such as the bme280 .. code-block:: python3 @@ -90,12 +90,22 @@ For humidity compensated raw gas readings, we'll need a secondary sensor such as while True: temperature = bme280.temperature humidity = bme280.relative_humidity + + # For compensated raw gas readings compensated_raw_gas = sgp.measure_raw(temperature = temperature, relative_humidity = humidity) + + time.sleep(1) + + # For Compensated voc index readings + voc_index = sgp.measure_index(temperature = temperature, relative_humidity = humidity) + print(compensated_raw_gas) + print(voc_index) print("") time.sleep(1) +It may take several minutes for the VOC index to start changing as it calibrates the baseline readings. Contributing ============ diff --git a/adafruit_sgp40.py b/adafruit_sgp40/__init__.py similarity index 88% rename from adafruit_sgp40.py rename to adafruit_sgp40/__init__.py index b40735c..0b1ebf1 100644 --- a/adafruit_sgp40.py +++ b/adafruit_sgp40/__init__.py @@ -31,7 +31,7 @@ """ from time import sleep from struct import unpack_from -import adafruit_bus_device.i2c_device as i2c_device +from adafruit_bus_device import i2c_device __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SGP40.git" @@ -111,6 +111,7 @@ def __init__(self, i2c, address=0x59): self.i2c_device = i2c_device.I2CDevice(i2c, address) self._command_buffer = bytearray(2) self._measure_command = _READ_CMD + self._voc_algorithm = None self.initialize() @@ -133,8 +134,6 @@ def initialize(self): raise RuntimeError("Feature set does not match: %s" % hex(featureset[0])) - # VocAlgorithm_init(&voc_algorithm_params) - # Self Test self._command_buffer[0] = 0x28 self._command_buffer[1] = 0x0E @@ -226,6 +225,34 @@ def measure_raw(self, temperature=25, relative_humidity=50): self._measure_command = bytearray(_cmd) return self.raw + def measure_index(self, temperature=25, relative_humidity=50): + """Measure VOC index after humidity compensation + :param float temperature: The temperature in degrees Celsius, defaults to :const:`25` + :param float relative_humidity: The relative humidity in percentage, defaults to :const:`50` + :note VOC index can indicate the quality of the air directly. + The larger the value, the worse the air quality. + :note 0-100, no need to ventilate, purify + :note 100-200, no need to ventilate, purify + :note 200-400, ventilate, purify + :note 00-500, ventilate, purify intensely + :return int The VOC index measured, ranged from 0 to 500 + """ + # import/setup algorithm only on use of index + # pylint: disable=import-outside-toplevel + from adafruit_sgp40.voc_algorithm import ( + VOCAlgorithm, + ) + + if self._voc_algorithm is None: + self._voc_algorithm = VOCAlgorithm() + self._voc_algorithm.vocalgorithm_init() + + raw = self.measure_raw(temperature, relative_humidity) + if raw < 0: + return -1 + voc_index = self._voc_algorithm.vocalgorithm_process(raw) + return voc_index + def _read_word_from_command( self, delay_ms=10, @@ -288,7 +315,7 @@ def _generate_crc(crc_buffer): if crc & 0x80: crc = ( crc << 1 - ) ^ 0x31 # 0x31 is the Seed for SGP40's CRC polynomial + ) ^ 0x31 # 0x31 is the Seed for SGP40's CRC polynomial else: crc = crc << 1 return crc & 0xFF # Returns only bottom 8 bits diff --git a/adafruit_sgp40/voc_algorithm.py b/adafruit_sgp40/voc_algorithm.py new file mode 100644 index 0000000..3106fa2 --- /dev/null +++ b/adafruit_sgp40/voc_algorithm.py @@ -0,0 +1,811 @@ +# SPDX-FileCopyrightText: Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) +# +# SPDX-License-Identifier: MIT +""" +`voc_algorithm` +================================================================================ + +Class and algorithm to convert Sensirion sgp40 raw reading to indexed voc readings. + + +* Author(s): yangfeng + +""" + +_VOCALGORITHM_SAMPLING_INTERVAL = const(1) +_VOCALGORITHM_INITIAL_BLACKOUT = const(45) +_VOCALGORITHM_VOC_INDEX_GAIN = const(230) +_VOCALGORITHM_SRAW_STD_INITIAL = const(50) +_VOCALGORITHM_SRAW_STD_BONUS = const(220) +_VOCALGORITHM_TAU_MEAN_VARIANCE_HOURS = const(12) +_VOCALGORITHM_TAU_INITIAL_MEAN = const(20) +_VOCALGORITHM_INITI_DURATION_MEAN = const(2700) +_VOCALGORITHM_INITI_TRANSITION_MEAN = 0.01 +_VOCALGORITHM_TAU_INITIAL_VARIANCE = const(2500) +_VOCALGORITHM_INITI_DURATION_VARIANCE = const(5220) +_VOCALGORITHM_INITI_TRANSITION_VARIANCE = 0.01 +_VOCALGORITHM_GATING_THRESHOLD = const(340) +_VOCALGORITHM_GATING_THRESHOLD_INITIAL = const(510) +_VOCALGORITHM_GATING_THRESHOLD_TRANSITION = 0.09 +_VOCALGORITHM_GATING_MAX_DURATION_MINUTES = const(180) +_VOCALGORITHM_GATING_MAX_RATIO = 0.3 +_VOCALGORITHM_SIGMOID_L = const(500) +_VOCALGORITHM_SIGMOID_K = -0.0065 +_VOCALGORITHM_SIGMOID_X0 = const(213) +_VOCALGORITHM_VOC_INDEX_OFFSET_DEFAULT = const(100) +_VOCALGORITHM_LP_TAU_FAST = const(20) +_VOCALGORITHM_LP_TAU_SLOW = const(500) +_VOCALGORITHM_LP_ALPHA = -0.2 +_VOCALGORITHM_PERSISTENCE_UPTIME_GAMMA = const(10800) +_VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING = const(64) +_VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX = const(32767) +_FIX16_MAXIMUM = const(0x7FFFFFFF) +_FIX16_MINIMUM = const(0x80000000) +_FIX16_OVERFLOW = const(0x80000000) +_FIX16_ONE = const(0x00010000) + + +class DFRobot_vocalgorithmParams: + """Class for voc index algorithm""" + + # pylint: disable=all + # Complex math conversion from C + + def __init__(self): + self.mvoc_index_offset = 0 + self.mtau_mean_variance_hours = 0 + self.mgating_max_duration_minutes = 0 + self.msraw_std_initial = 0 + self.muptime = 0 + self.msraw = 0 + self.mvoc_index = 0 + self.m_mean_variance_estimator_gating_max_duration_minutes = 0 + self.m_mean_variance_estimator_initialized = 0 + self.m_mean_variance_estimator_mean = 0 + self.m_mean_variance_estimator_sraw_offset = 0 + self.m_mean_variance_estimator_std = 0 + self.m_mean_variance_estimator_gamma = 0 + self.m_mean_variance_estimator_gamma_initial_mean = 0 + self.m_mean_variance_estimator_gamma_initial_variance = 0 + self.m_mean_variance_estimator_gamma_mean = 0 + self.m_mean_variance_estimator__gamma_variance = 0 + self.m_mean_variance_estimator_uptime_gamma = 0 + self.m_mean_variance_estimator_uptime_gating = 0 + self.m_mean_variance_estimator_gating_duration_minutes = 0 + self.m_mean_variance_estimator_sigmoid_l = 0 + self.m_mean_variance_estimator_sigmoid_k = 0 + self.m_mean_variance_estimator_sigmoid_x0 = 0 + self.m_mox_model_sraw_mean = 0 + self.m_sigmoid_scaled_offset = 0 + self.m_adaptive_lowpass_a1 = 0 + self.m_adaptive_lowpass_a2 = 0 + self.m_adaptive_lowpass_initialized = 0 + self.m_adaptive_lowpass_x1 = 0 + self.m_adaptive_lowpass_x2 = 0 + self.m_adaptive_lowpass_x3 = 0 + + +class VOCAlgorithm: + def __init__(self): + self.params = DFRobot_vocalgorithmParams() + + def _f16(self, x): + if x >= 0: + return int((x) * 65536.0 + 0.5) + else: + return int((x) * 65536.0 - 0.5) + + def _fix16_from_int(self, a): + return int(a * _FIX16_ONE) + + def _fix16_cast_to_int(self, a): + return int(a) >> 16 + + def _fix16_mul(self, inarg0, inarg1): + inarg0 = int(inarg0) + inarg1 = int(inarg1) + A = inarg0 >> 16 + if inarg0 < 0: + B = (inarg0 & 0xFFFFFFFF) & 0xFFFF + else: + B = inarg0 & 0xFFFF + C = inarg1 >> 16 + if inarg1 < 0: + D = (inarg1 & 0xFFFFFFFF) & 0xFFFF + else: + D = inarg1 & 0xFFFF + AC = A * C + AD_CB = A * D + C * B + BD = B * D + product_hi = AC + (AD_CB >> 16) + ad_cb_temp = ((AD_CB) << 16) & 0xFFFFFFFF + product_lo = ((BD + ad_cb_temp)) & 0xFFFFFFFF + if product_lo < BD: + product_hi = product_hi + 1 + if (product_hi >> 31) != (product_hi >> 15): + return _FIX16_OVERFLOW + product_lo_tmp = product_lo & 0xFFFFFFFF + product_lo = (product_lo - 0x8000) & 0xFFFFFFFF + product_lo = (product_lo - ((product_hi & 0xFFFFFFFF) >> 31)) & 0xFFFFFFFF + if product_lo > product_lo_tmp: + product_hi = product_hi - 1 + result = (product_hi << 16) | (product_lo >> 16) + result += 1 + return result + + def _fix16_div(self, a, b): + a = int(a) + b = int(b) + if b == 0: + return _FIX16_MINIMUM + if a >= 0: + remainder = a + else: + remainder = (a * (-1)) & 0xFFFFFFFF + if b >= 0: + divider = b + else: + divider = (b * (-1)) & 0xFFFFFFFF + quotient = 0 + bit = 0x10000 + while divider < remainder: + divider = divider << 1 + bit <<= 1 + if not bit: + return _FIX16_OVERFLOW + if divider & 0x80000000: + if remainder >= divider: + quotient |= bit + remainder -= divider + divider >>= 1 + bit >>= 1 + while bit and remainder: + if remainder >= divider: + quotient |= bit + remainder -= divider + remainder <<= 1 + bit >>= 1 + if remainder >= divider: + quotient += 1 + result = quotient + if (a ^ b) & 0x80000000: + if result == _FIX16_MINIMUM: + return _FIX16_OVERFLOW + result = -result + return result + + def _fix16_sqrt(self, x): + x = int(x) + num = x & 0xFFFFFFFF + result = 0 + bit = 1 << 30 + while bit > num: + bit >>= 2 + for n in range(0, 2): + while bit: + if num >= result + bit: + num = num - (result + bit) & 0xFFFFFFFF + result = (result >> 1) + bit + else: + result = result >> 1 + bit >>= 2 + if n == 0: + if num > 65535: + num = (num - result) & 0xFFFFFFFF + num = ((num << 16) - 0x8000) & 0xFFFFFFFF + result = ((result << 16) + 0x8000) & 0xFFFFFFFF + else: + num = (num << 16) & 0xFFFFFFFF + result = (result << 16) & 0xFFFFFFFF + bit = 1 << 14 + if num > result: + result += 1 + return result + + def _fix16_exp(self, x): + x = int(x) + exp_pos_values = [ + self._f16(2.7182818), + self._f16(1.1331485), + self._f16(1.0157477), + self._f16(1.0019550), + ] + exp_neg_values = [ + self._f16(0.3678794), + self._f16(0.8824969), + self._f16(0.9844964), + self._f16(0.9980488), + ] + if x >= self._f16(10.3972): + return _FIX16_MAXIMUM + if x <= self._f16(-11.7835): + return 0 + if x < 0: + x = -x + exp_values = exp_neg_values + else: + exp_values = exp_pos_values + res = _FIX16_ONE + arg = _FIX16_ONE + for i in range(0, 4): + while x >= arg: + res = self._fix16_mul(res, exp_values[i]) + x -= arg + arg >>= 3 + return res + + def vocalgorithm_init(self): + self.params.mvoc_index_offset = self._f16( + _VOCALGORITHM_VOC_INDEX_OFFSET_DEFAULT + ) + self.params.mtau_mean_variance_hours = self._f16( + _VOCALGORITHM_TAU_MEAN_VARIANCE_HOURS + ) + self.params.mgating_max_duration_minutes = self._f16( + _VOCALGORITHM_GATING_MAX_DURATION_MINUTES + ) + self.params.msraw_std_initial = self._f16(_VOCALGORITHM_SRAW_STD_INITIAL) + self.params.muptime = self._f16(0.0) + self.params.msraw = self._f16(0.0) + self.params.mvoc_index = 0 + self._vocalgorithm__init_instances() + + def _vocalgorithm__init_instances(self): + self._vocalgorithm__mean_variance_estimator__init() + self._vocalgorithm__mean_variance_estimator__set_parameters( + self._f16(_VOCALGORITHM_SRAW_STD_INITIAL), + self.params.mtau_mean_variance_hours, + self.params.mgating_max_duration_minutes, + ) + self._vocalgorithm__mox_model__init() + self._vocalgorithm__mox_model__set_parameters( + self._vocalgorithm__mean_variance_estimator__get_std(), + self._vocalgorithm__mean_variance_estimator__get_mean(), + ) + self._vocalgorithm__sigmoid_scaled__init() + self._vocalgorithm__sigmoid_scaled__set_parameters( + self.params.mvoc_index_offset + ) + self._vocalgorithm__adaptive_lowpass__init() + self._vocalgorithm__adaptive_lowpass__set_parameters() + + def _vocalgorithm_get_states(self, state0, state1): + state0 = self._vocalgorithm__mean_variance_estimator__get_mean() + state1 = self._vocalgorithm__mean_variance_estimator__get_std() + return state0, state1 + + def _vocalgorithm_set_states(self, state0, state1): + self._vocalgorithm__mean_variance_estimator__set_states( + self.params, + state0, + state1, + self._f16(_VOCALGORITHM_PERSISTENCE_UPTIME_GAMMA), + ) + self.params.msraw = state0 + + def _vocalgorithm_set_tuning_parameters( + self, + voc_index_offset, + learning_time_hours, + gating_max_duration_minutes, + std_initial, + ): + self.params.mvoc_index_offset = self._fix16_from_int(voc_index_offset) + self.params.mtau_mean_variance_hours = self._fix16_from_int(learning_time_hours) + self.params.mgating_max_duration_minutes = self._fix16_from_int( + gating_max_duration_minutes + ) + self.params.msraw_std_initial = self._fix16_from_int(std_initial) + self._vocalgorithm__init_instances() + + def vocalgorithm_process(self, sraw): + if self.params.muptime <= self._f16(_VOCALGORITHM_INITIAL_BLACKOUT): + self.params.muptime = self.params.muptime + self._f16( + _VOCALGORITHM_SAMPLING_INTERVAL + ) + else: + if (sraw > 0) and (sraw < 65000): + if sraw < 20001: + sraw = 20001 + elif sraw > 52767: + sraw = 52767 + self.params.msraw = self._fix16_from_int((sraw - 20000)) + self.params.mvoc_index = self._vocalgorithm__mox_model__process( + self.params.msraw + ) + self.params.mvoc_index = self._vocalgorithm__sigmoid_scaled__process( + self.params.mvoc_index + ) + self.params.mvoc_index = self._vocalgorithm__adaptive_lowpass__process( + self.params.mvoc_index + ) + if self.params.mvoc_index < self._f16(0.5): + self.params.mvoc_index = self._f16(0.5) + if self.params.msraw > self._f16(0.0): + self._vocalgorithm__mean_variance_estimator__process( + self.params.msraw, self.params.mvoc_index + ) + self._vocalgorithm__mox_model__set_parameters( + self._vocalgorithm__mean_variance_estimator__get_std(), + self._vocalgorithm__mean_variance_estimator__get_mean(), + ) + voc_index = self._fix16_cast_to_int((self.params.mvoc_index + self._f16(0.5))) + return voc_index + + def _vocalgorithm__mean_variance_estimator__init(self): + self._vocalgorithm__mean_variance_estimator__set_parameters( + self._f16(0.0), self._f16(0.0), self._f16(0.0) + ) + self._vocalgorithm__mean_variance_estimator___init_instances() + + def _vocalgorithm__mean_variance_estimator___init_instances(self): + self._vocalgorithm__mean_variance_estimator___sigmoid__init() + + def _vocalgorithm__mean_variance_estimator__set_parameters( + self, std_initial, tau_mean_variance_hours, gating_max_duration_minutes + ): + self.params.m_mean_variance_estimator_gating_max_duration_minutes = ( + gating_max_duration_minutes + ) + self.params.m_mean_variance_estimator_initialized = 0 + self.params.m_mean_variance_estimator_mean = self._f16(0.0) + self.params.m_mean_variance_estimator_sraw_offset = self._f16(0.0) + self.params.m_mean_variance_estimator_std = std_initial + self.params.m_mean_variance_estimator_gamma = self._fix16_div( + self._f16( + ( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING + * (_VOCALGORITHM_SAMPLING_INTERVAL / 3600.0) + ) + ), + ( + tau_mean_variance_hours + + self._f16((_VOCALGORITHM_SAMPLING_INTERVAL / 3600.0)) + ), + ) + self.params.m_mean_variance_estimator_gamma_initial_mean = self._f16( + ( + ( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING + * _VOCALGORITHM_SAMPLING_INTERVAL + ) + / (_VOCALGORITHM_TAU_INITIAL_MEAN + _VOCALGORITHM_SAMPLING_INTERVAL) + ) + ) + self.params.m_mean_variance_estimator_gamma_initial_variance = self._f16( + ( + ( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING + * _VOCALGORITHM_SAMPLING_INTERVAL + ) + / (_VOCALGORITHM_TAU_INITIAL_VARIANCE + _VOCALGORITHM_SAMPLING_INTERVAL) + ) + ) + self.params.m_mean_variance_estimator_gamma_mean = self._f16(0.0) + self.params.m_mean_variance_estimator__gamma_variance = self._f16(0.0) + self.params.m_mean_variance_estimator_uptime_gamma = self._f16(0.0) + self.params.m_mean_variance_estimator_uptime_gating = self._f16(0.0) + self.params.m_mean_variance_estimator_gating_duration_minutes = self._f16(0.0) + + def _vocalgorithm__mean_variance_estimator__set_states( + self, mean, std, uptime_gamma + ): + self.params.m_mean_variance_estimator_mean = mean + self.params.m_mean_variance_estimator_std = std + self.params.m_mean_variance_estimator_uptime_gamma = uptime_gamma + self.params.m_mean_variance_estimator_initialized = True + + def _vocalgorithm__mean_variance_estimator__get_std(self): + return self.params.m_mean_variance_estimator_std + + def _vocalgorithm__mean_variance_estimator__get_mean(self): + return ( + self.params.m_mean_variance_estimator_mean + + self.params.m_mean_variance_estimator_sraw_offset + ) + + def _vocalgorithm__mean_variance_estimator___calculate_gamma( + self, voc_index_from_prior + ): + uptime_limit = self._f16( + ( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX + - _VOCALGORITHM_SAMPLING_INTERVAL + ) + ) + if self.params.m_mean_variance_estimator_uptime_gamma < uptime_limit: + self.params.m_mean_variance_estimator_uptime_gamma = ( + self.params.m_mean_variance_estimator_uptime_gamma + + self._f16(_VOCALGORITHM_SAMPLING_INTERVAL) + ) + + if self.params.m_mean_variance_estimator_uptime_gating < uptime_limit: + self.params.m_mean_variance_estimator_uptime_gating = ( + self.params.m_mean_variance_estimator_uptime_gating + + self._f16(_VOCALGORITHM_SAMPLING_INTERVAL) + ) + + self._vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self._f16(1.0), + self._f16(_VOCALGORITHM_INITI_DURATION_MEAN), + self._f16(_VOCALGORITHM_INITI_TRANSITION_MEAN), + ) + sigmoid_gamma_mean = ( + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + self.params.m_mean_variance_estimator_uptime_gamma + ) + ) + gamma_mean = self.params.m_mean_variance_estimator_gamma + ( + self._fix16_mul( + ( + self.params.m_mean_variance_estimator_gamma_initial_mean + - self.params.m_mean_variance_estimator_gamma + ), + sigmoid_gamma_mean, + ) + ) + gating_threshold_mean = self._f16(_VOCALGORITHM_GATING_THRESHOLD) + ( + self._fix16_mul( + self._f16( + ( + _VOCALGORITHM_GATING_THRESHOLD_INITIAL + - _VOCALGORITHM_GATING_THRESHOLD + ) + ), + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + self.params.m_mean_variance_estimator_uptime_gating + ), + ) + ) + self._vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self._f16(1.0), + gating_threshold_mean, + self._f16(_VOCALGORITHM_GATING_THRESHOLD_TRANSITION), + ) + + sigmoid_gating_mean = ( + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + voc_index_from_prior + ) + ) + self.params.m_mean_variance_estimator_gamma_mean = self._fix16_mul( + sigmoid_gating_mean, gamma_mean + ) + + self._vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self._f16(1.0), + self._f16(_VOCALGORITHM_INITI_DURATION_VARIANCE), + self._f16(_VOCALGORITHM_INITI_TRANSITION_VARIANCE), + ) + + sigmoid_gamma_variance = ( + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + self.params.m_mean_variance_estimator_uptime_gamma + ) + ) + + gamma_variance = self.params.m_mean_variance_estimator_gamma + ( + self._fix16_mul( + ( + self.params.m_mean_variance_estimator_gamma_initial_variance + - self.params.m_mean_variance_estimator_gamma + ), + (sigmoid_gamma_variance - sigmoid_gamma_mean), + ) + ) + + gating_threshold_variance = self._f16(_VOCALGORITHM_GATING_THRESHOLD) + ( + self._fix16_mul( + self._f16( + ( + _VOCALGORITHM_GATING_THRESHOLD_INITIAL + - _VOCALGORITHM_GATING_THRESHOLD + ) + ), + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + self.params.m_mean_variance_estimator_uptime_gating + ), + ) + ) + + self._vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self._f16(1.0), + gating_threshold_variance, + self._f16(_VOCALGORITHM_GATING_THRESHOLD_TRANSITION), + ) + + sigmoid_gating_variance = ( + self._vocalgorithm__mean_variance_estimator___sigmoid__process( + voc_index_from_prior + ) + ) + + self.params.m_mean_variance_estimator__gamma_variance = self._fix16_mul( + sigmoid_gating_variance, gamma_variance + ) + + self.params.m_mean_variance_estimator_gating_duration_minutes = ( + self.params.m_mean_variance_estimator_gating_duration_minutes + + ( + self._fix16_mul( + self._f16((_VOCALGORITHM_SAMPLING_INTERVAL / 60.0)), + ( + ( + self._fix16_mul( + (self._f16(1.0) - sigmoid_gating_mean), + self._f16((1.0 + _VOCALGORITHM_GATING_MAX_RATIO)), + ) + ) + - self._f16(_VOCALGORITHM_GATING_MAX_RATIO) + ), + ) + ) + ) + + if self.params.m_mean_variance_estimator_gating_duration_minutes < self._f16( + 0.0 + ): + self.params.m_mean_variance_estimator_gating_duration_minutes = self._f16( + 0.0 + ) + + if ( + self.params.m_mean_variance_estimator_gating_duration_minutes + > self.params.m_mean_variance_estimator_gating_max_duration_minutes + ): + self.params.m_mean_variance_estimator_uptime_gating = self._f16(0.0) + + def _vocalgorithm__mean_variance_estimator__process( + self, sraw, voc_index_from_prior + ): + if self.params.m_mean_variance_estimator_initialized == 0: + self.params.m_mean_variance_estimator_initialized = 1 + self.params.m_mean_variance_estimator_sraw_offset = sraw + self.params.m_mean_variance_estimator_mean = self._f16(0.0) + else: + if (self.params.m_mean_variance_estimator_mean >= self._f16(100.0)) or ( + self.params.m_mean_variance_estimator_mean <= self._f16(-100.0) + ): + self.params.m_mean_variance_estimator_sraw_offset = ( + self.params.m_mean_variance_estimator_sraw_offset + + self.params.m_mean_variance_estimator_mean + ) + self.params.m_mean_variance_estimator_mean = self._f16(0.0) + + sraw = sraw - self.params.m_mean_variance_estimator_sraw_offset + self._vocalgorithm__mean_variance_estimator___calculate_gamma( + voc_index_from_prior + ) + delta_sgp = self._fix16_div( + (sraw - self.params.m_mean_variance_estimator_mean), + self._f16(_VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING), + ) + if delta_sgp < self._f16(0.0): + c = self.params.m_mean_variance_estimator_std - delta_sgp + else: + c = self.params.m_mean_variance_estimator_std + delta_sgp + additional_scaling = self._f16(1.0) + if c > self._f16(1440.0): + additional_scaling = self._f16(4.0) + self.params.m_mean_variance_estimator_std = self._fix16_mul( + self._fix16_sqrt( + ( + self._fix16_mul( + additional_scaling, + ( + self._f16( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING + ) + - self.params.m_mean_variance_estimator__gamma_variance + ), + ) + ) + ), + self._fix16_sqrt( + ( + ( + self._fix16_mul( + self.params.m_mean_variance_estimator_std, + ( + self._fix16_div( + self.params.m_mean_variance_estimator_std, + ( + self._fix16_mul( + self._f16( + _VOCALGORITHM_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING + ), + additional_scaling, + ) + ), + ) + ), + ) + ) + + ( + self._fix16_mul( + ( + self._fix16_div( + ( + self._fix16_mul( + self.params.m_mean_variance_estimator__gamma_variance, + delta_sgp, + ) + ), + additional_scaling, + ) + ), + delta_sgp, + ) + ) + ) + ), + ) + self.params.m_mean_variance_estimator_mean = ( + self.params.m_mean_variance_estimator_mean + + ( + self._fix16_mul( + self.params.m_mean_variance_estimator_gamma_mean, delta_sgp + ) + ) + ) + + def _vocalgorithm__mean_variance_estimator___sigmoid__init(self): + self._vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self._f16(0.0), self._f16(0.0), self._f16(0.0) + ) + + def _vocalgorithm__mean_variance_estimator___sigmoid__set_parameters( + self, L, X0, K + ): + self.params.m_mean_variance_estimator_sigmoid_l = L + self.params.m_mean_variance_estimator_sigmoid_k = K + self.params.m_mean_variance_estimator_sigmoid_x0 = X0 + + def _vocalgorithm__mean_variance_estimator___sigmoid__process(self, sample): + x = self._fix16_mul( + self.params.m_mean_variance_estimator_sigmoid_k, + (sample - self.params.m_mean_variance_estimator_sigmoid_x0), + ) + if x < self._f16(-50.0): + return self.params.m_mean_variance_estimator_sigmoid_l + elif x > self._f16(50.0): + return self._f16(0.0) + else: + return self._fix16_div( + self.params.m_mean_variance_estimator_sigmoid_l, + (self._f16(1.0) + self._fix16_exp(x)), + ) + + def _vocalgorithm__mox_model__init(self): + self._vocalgorithm__mox_model__set_parameters(self._f16(1.0), self._f16(0.0)) + + def _vocalgorithm__mox_model__set_parameters(self, SRAW_STD, SRAW_MEAN): + self.params.m_mox_model_sraw_std = SRAW_STD + self.params.m_mox_model_sraw_mean = SRAW_MEAN + + def _vocalgorithm__mox_model__process(self, sraw): + return self._fix16_mul( + ( + self._fix16_div( + (sraw - self.params.m_mox_model_sraw_mean), + ( + -( + self.params.m_mox_model_sraw_std + + self._f16(_VOCALGORITHM_SRAW_STD_BONUS) + ) + ), + ) + ), + self._f16(_VOCALGORITHM_VOC_INDEX_GAIN), + ) + + def _vocalgorithm__sigmoid_scaled__init(self): + self._vocalgorithm__sigmoid_scaled__set_parameters(self._f16(0.0)) + + def _vocalgorithm__sigmoid_scaled__set_parameters(self, offset): + self.params.m_sigmoid_scaled_offset = offset + + def _vocalgorithm__sigmoid_scaled__process(self, sample): + x = self._fix16_mul( + self._f16(_VOCALGORITHM_SIGMOID_K), + (sample - self._f16(_VOCALGORITHM_SIGMOID_X0)), + ) + if x < self._f16(-50.0): + return self._f16(_VOCALGORITHM_SIGMOID_L) + elif x > self._f16(50.0): + return self._f16(0.0) + else: + if sample >= self._f16(0.0): + shift = self._fix16_div( + ( + self._f16(_VOCALGORITHM_SIGMOID_L) + - ( + self._fix16_mul( + self._f16(5.0), self.params.m_sigmoid_scaled_offset + ) + ) + ), + self._f16(4.0), + ) + return ( + self._fix16_div( + (self._f16(_VOCALGORITHM_SIGMOID_L) + shift), + (self._f16(1.0) + self._fix16_exp(x)), + ) + ) - shift + else: + return self._fix16_mul( + ( + self._fix16_div( + self.params.m_sigmoid_scaled_offset, + self._f16(_VOCALGORITHM_VOC_INDEX_OFFSET_DEFAULT), + ) + ), + ( + self._fix16_div( + self._f16(_VOCALGORITHM_SIGMOID_L), + (self._f16(1.0) + self._fix16_exp(x)), + ) + ), + ) + + def _vocalgorithm__adaptive_lowpass__init(self): + self._vocalgorithm__adaptive_lowpass__set_parameters() + + def _vocalgorithm__adaptive_lowpass__set_parameters(self): + self.params.m_adaptive_lowpass_a1 = self._f16( + ( + _VOCALGORITHM_SAMPLING_INTERVAL + / (_VOCALGORITHM_LP_TAU_FAST + _VOCALGORITHM_SAMPLING_INTERVAL) + ) + ) + self.params.m_adaptive_lowpass_a2 = self._f16( + ( + _VOCALGORITHM_SAMPLING_INTERVAL + / (_VOCALGORITHM_LP_TAU_SLOW + _VOCALGORITHM_SAMPLING_INTERVAL) + ) + ) + self.params.m_adaptive_lowpass_initialized = 0 + + def _vocalgorithm__adaptive_lowpass__process(self, sample): + if self.params.m_adaptive_lowpass_initialized == 0: + self.params.m_adaptive_lowpass_x1 = sample + self.params.m_adaptive_lowpass_x2 = sample + self.params.m_adaptive_lowpass_x3 = sample + self.params.m_adaptive_lowpass_initialized = 1 + self.params.m_adaptive_lowpass_x1 = ( + self._fix16_mul( + (self._f16(1.0) - self.params.m_adaptive_lowpass_a1), + self.params.m_adaptive_lowpass_x1, + ) + ) + (self._fix16_mul(self.params.m_adaptive_lowpass_a1, sample)) + + self.params.m_adaptive_lowpass_x2 = ( + self._fix16_mul( + (self._f16(1.0) - self.params.m_adaptive_lowpass_a2), + self.params.m_adaptive_lowpass_x2, + ) + ) + (self._fix16_mul(self.params.m_adaptive_lowpass_a2, sample)) + + abs_delta = ( + self.params.m_adaptive_lowpass_x1 - self.params.m_adaptive_lowpass_x2 + ) + + if abs_delta < self._f16(0.0): + abs_delta = -abs_delta + F1 = self._fix16_exp( + (self._fix16_mul(self._f16(_VOCALGORITHM_LP_ALPHA), abs_delta)) + ) + tau_a = ( + self._fix16_mul( + self._f16((_VOCALGORITHM_LP_TAU_SLOW - _VOCALGORITHM_LP_TAU_FAST)), F1 + ) + ) + self._f16(_VOCALGORITHM_LP_TAU_FAST) + a3 = self._fix16_div( + self._f16(_VOCALGORITHM_SAMPLING_INTERVAL), + (self._f16(_VOCALGORITHM_SAMPLING_INTERVAL) + tau_a), + ) + self.params.m_adaptive_lowpass_x3 = ( + self._fix16_mul((self._f16(1.0) - a3), self.params.m_adaptive_lowpass_x3) + ) + (self._fix16_mul(a3, sample)) + return self.params.m_adaptive_lowpass_x3 diff --git a/examples/sgp40_indextest.py b/examples/sgp40_indextest.py new file mode 100644 index 0000000..5d9dd4d --- /dev/null +++ b/examples/sgp40_indextest.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2020 by Bryan Siepert for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense +import time +import board +import adafruit_bme280 +import adafruit_sgp40 + +# Boards i2c bus +i2c = board.I2C() # uses board.SCL and board.SDA +sgp = adafruit_sgp40.SGP40(i2c) + +# Humidity sensor for compensated Readings +bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c) + +while True: + temperature = bme280.temperature + humidity = bme280.relative_humidity + + # For compensated raw gas readings + compensated_raw_gas = sgp.measure_raw( + temperature=temperature, relative_humidity=humidity + ) + + time.sleep(1) + + # For Compensated voc index readings + # It may take several minutes for the VOC index to start changing + # as it calibrates the baseline readings. + voc_index = sgp.measure_index(temperature=temperature, relative_humidity=humidity) + + print("Raw Data:", compensated_raw_gas) + print("VOC Index:", voc_index) + print("") + time.sleep(1)