/// © MiroZ 2024 #include #include "Bme68x.h" static const char *TAG = "Bme68x"; #define NEW_GAS_MEAS (BME68X_GASM_VALID_MSK | BME68X_HEAT_STAB_MSK | BME68X_NEW_DATA_MSK) // Define all 8 profiles const Bme68x::HeaterProfile Bme68x::profiles[8] = { /* Profile 0 - Index 100 (160°C, 198ms): Light alcohols (methanol, ethanol vapors) - Index 101 (170°C, 297ms): Acetone, light ketones - Index 102 (180°C, 396ms): Isopropanol, cleaning alcohol vapors - Index 103 (190°C, 594ms): Formaldehyde, light aldehydes - Index 104 (200°C, 792ms): Ethyl acetate, light esters - Index 105 (210°C, 990ms): Benzene, light aromatic hydrocarbons - Index 106 (220°C, 1188ms): Toluene, paint solvents - Index 107 (230°C, 1386ms): Xylene, heavier paint thinners - Index 108 (240°C, 1584ms): Styrene, plastic outgassing - Index 109 (200°C, 1980ms): Extended detection of persistent light VOCs */ { .temp_prof = { 160, 170, 180, 190, 200, 210, 220, 230, 240, 200 }, .mul_prof = { 2, 3, 4, 6, 8, 10, 12, 14, 16, 20 } }, /* ## Profile 1 (Indexes 110-119) - Medium-Low Temperature Range **Temperature Range: 220-340°C | Duration Range: 297-2376ms** - Index 110 (220°C, 297ms): Acetic acid, vinegar vapors - Index 111 (240°C, 396ms): Ethylene glycol, antifreeze vapors - Index 112 (260°C, 495ms): Propylene glycol, vaping compounds - Index 113 (280°C, 594ms): Butanol, heavier alcohols - Index 114 (300°C, 792ms): Cooking oils, fatty acid vapors - Index 115 (320°C, 990ms): Ammonia, cleaning product vapors - Index 116 (300°C, 1188ms): Extended cooking oil detection - Index 117 (280°C, 1584ms): Diesel fuel vapors - Index 118 (340°C, 594ms): Naphthalene, mothball compounds - Index 119 (260°C, 2376ms): Extended glycol detection */ { .temp_prof = { 220, 240, 260, 280, 300, 320, 300, 280, 340, 260 }, .mul_prof = { 3, 4, 5, 6, 8, 10, 12, 16, 6, 24 } }, /* ## Profile 2 (Indexes 120-129) - Medium Temperature Range **Temperature Range: 280-360°C | Duration Range: 396-1980ms** - Index 120 (280°C, 396ms): Chloroform, dry cleaning solvents - Index 121 (300°C, 495ms): Gasoline vapors, light hydrocarbons - Index 122 (320°C, 594ms): Methylene chloride, paint strippers - Index 123 (340°C, 693ms): Tetrachloroethylene, dry cleaning - Index 124 (360°C, 792ms): Carbon monoxide, combustion gases - Index 125 (320°C, 1188ms): Natural gas (methane) leaks - Index 126 (340°C, 990ms): Propane, LPG vapors - Index 127 (300°C, 1584ms): Extended gasoline vapor detection - Index 128 (350°C, 594ms): Hydrogen sulfide, sewer gases - Index 129 (330°C, 1980ms): Extended natural gas detection */ { .temp_prof = { 280, 300, 320, 340, 360, 320, 340, 300, 350, 330 }, .mul_prof = { 4, 5, 6, 7, 8, 12, 10, 16, 6, 20 } }, /* ## Profile 3 (Indexes 130-139) - Medium-High Temperature Range **Temperature Range: 340-380°C | Duration Range: 396-2376ms** - Index 130 (340°C, 396ms): Hydrogen gas, battery outgassing - Index 131 (360°C, 495ms): Sulfur dioxide, industrial emissions - Index 132 (350°C, 594ms): Nitrogen oxides, car exhaust - Index 133 (370°C, 693ms): Ozone, electrical arc detection - Index 134 (340°C, 990ms): Extended hydrogen detection - Index 135 (380°C, 495ms): High-temperature combustion products - Index 136 (360°C, 1188ms): Extended SO2 detection - Index 137 (350°C, 1386ms): Extended NOx detection - Index 138 (370°C, 792ms): Chlorine gas, pool chemicals - Index 139 (340°C, 2376ms): Extended battery gas detection */ { .temp_prof = { 340, 360, 350, 370, 340, 380, 360, 350, 370, 340 }, .mul_prof = { 4, 5, 6, 7, 10, 5, 12, 14, 8, 24 } }, /* ## Profile 4 (Indexes 140-149) - Broad Spectrum Detection **Temperature Range: 250-370°C | Duration Range: 594-1980ms** - Index 140 (250°C, 594ms): Ethanol breath detection - Index 141 (280°C, 792ms): Cannabis/marijuana compounds - Index 142 (320°C, 891ms): Tobacco smoke compounds - Index 143 (350°C, 990ms): Burning paper/wood smoke - Index 144 (300°C, 1188ms): Cooking spice vapors - Index 145 (370°C, 693ms): Electrical burning smell - Index 146 (330°C, 1386ms): Mold/mildew compounds - Index 147 (360°C, 891ms): Plastic burning detection - Index 148 (340°C, 1584ms): Food spoilage compounds - Index 149 (320°C, 1980ms): Extended tobacco detection */ { .temp_prof = { 250, 280, 320, 350, 300, 370, 330, 360, 340, 320 }, .mul_prof = { 6, 8, 9, 10, 12, 7, 14, 9, 16, 20 } }, /* ## Profile 5 (Indexes 150-159) - Complex VOC Detection **Temperature Range: 260-380°C | Duration Range: 495-1584ms** - Index 150 (260°C, 495ms): Perfume/cologne compounds - Index 151 (300°C, 594ms): Laundry detergent vapors - Index 152 (340°C, 693ms): Fabric softener chemicals - Index 153 (370°C, 792ms): Bleach/chlorine compounds - Index 154 (320°C, 1188ms): Room deodorizer chemicals - Index 155 (360°C, 990ms): Disinfectant vapors - Index 156 (350°C, 1089ms): Medical alcohol/sanitizer - Index 157 (380°C, 594ms): High-temp disinfection products - Index 158 (330°C, 1584ms): Air freshener compounds - Index 159 (370°C, 1188ms): Extended bleach detection */ { .temp_prof = { 260, 300, 340, 370, 320, 360, 350, 380, 330, 370 }, .mul_prof = { 5, 6, 7, 8, 12, 10, 11, 6, 16, 12 } }, /* ## Profile 6 (Indexes 160-169) - High-Temperature VOCs **Temperature Range: 300-380°C | Duration Range: 495-1584ms** - Index 160 (300°C, 495ms): Printer toner compounds - Index 161 (330°C, 594ms): Copier ozone/chemicals - Index 162 (360°C, 693ms): Electronic component outgassing - Index 163 (350°C, 792ms): Flux vapors from soldering - Index 164 (370°C, 891ms): Hot plastic from electronics - Index 165 (340°C, 1188ms): Adhesive/glue vapors - Index 166 (380°C, 594ms): High-temp electronic burning - Index 167 (360°C, 1386ms): Extended electronics detection - Index 168 (350°C, 990ms): Thermal paste vapors - Index 169 (370°C, 1584ms): Extended adhesive detection */ { .temp_prof = { 300, 330, 360, 350, 370, 340, 380, 360, 350, 370 }, .mul_prof = { 5, 6, 7, 8, 9, 12, 6, 14, 10, 16 } }, /* ## Profile 7 (Indexes 170-179) - Combustion & High-Temp Detection **Temperature Range: 340-380°C | Duration Range: 792-1782ms** - Index 170 (350°C, 792ms): Diesel exhaust particles - Index 171 (370°C, 891ms): Gasoline engine exhaust - Index 172 (360°C, 990ms): Motorcycle exhaust compounds - Index 173 (380°C, 693ms): Industrial furnace emissions - Index 174 (340°C, 1584ms): Wood stove/fireplace smoke - Index 175 (370°C, 1188ms): Barbecue/grilling smoke - Index 176 (360°C, 1386ms): Coal burning compounds - Index 177 (350°C, 1782ms): Extended wood smoke detection - Index 178 (380°C, 792ms): Kerosene/jet fuel vapors - Index 179 (370°C, 1584ms): Extended BBQ smoke detection */ { .temp_prof = { 350, 370, 360, 380, 340, 370, 360, 350, 380, 370 }, .mul_prof = { 8, 9, 10, 7, 16, 12, 14, 18, 8, 20 } } }; Bme68x::Bme68x(TwoWire & bus) : m_bus(bus) { m_sensor = new MyLibs::Bme68x(); } void Bme68x::switchToProfile(uint8_t profile_index) { if (profile_index >= 8) { ESP_LOGE(TAG, "Invalid profile index: %d", profile_index); return; } const HeaterProfile& profile = profiles[profile_index]; ESP_LOGI(TAG, "Switching to profile %d", profile_index); // Put sensor to sleep before changing configuration m_sensor->setOpMode(BME68X_SLEEP_MODE); // Calculate shared heater duration - use a reasonable base value like original uint32_t tph_dur_us = m_sensor->getMeasDur(BME68X_PARALLEL_MODE); uint16_t tph_dur_ms = tph_dur_us / 1000; // Use a fixed reasonable shared heater duration similar to original (around 50-100ms) uint16_t sharedHeatrDur = 140 - tph_dur_ms; // Use original approach // Ensure it's positive and reasonable if (sharedHeatrDur > 200 || (int16_t)sharedHeatrDur < 10) { sharedHeatrDur = 99; // Default to original working value } ESP_LOGI(TAG, "Profile %d: TPH duration=%u ms, shared heater duration=%u ms", profile_index, tph_dur_ms, sharedHeatrDur); // Copy arrays to non-const arrays for the API call uint16_t tempProf[10]; uint16_t mulProf[10]; for (int i = 0; i < 10; i++) { tempProf[i] = profile.temp_prof[i]; mulProf[i] = profile.mul_prof[i]; } // Set the heater profile m_sensor->setHeaterProf(tempProf, mulProf, sharedHeatrDur, 10); // Check sensor status after configuration //int8_t status = m_sensor->checkStatus(); //ESP_LOGI(TAG, "Sensor status after heater config: %d", status); // Restart parallel mode m_sensor->setOpMode(BME68X_PARALLEL_MODE); // Clear any leftover data from previous profile delay(100); // Give sensor time to start new profile while(m_sensor->fetchData() > 0) { MyLibs::bme68xData dummy; uint8_t left; do { left = m_sensor->getData(dummy); } while(left > 0); } // Check status after mode change //status = m_sensor->checkStatus(); //ESP_LOGI(TAG, "Sensor status after mode change: %d", status); m_current_profile = profile_index; } void Bme68x::switchToNextProfile() { uint8_t next_profile = (m_current_profile + 1) % 8; switchToProfile(next_profile); ESP_LOGI(TAG, "Cycle completed, switched to profile %d", next_profile); } bool Bme68x::init() { m_sensor->begin(0x77, m_bus); m_sensor->setTPH(); // Start with profile 0 switchToProfile(0); m_operational = m_sensor->checkStatus() == BME68X_OK; ESP_LOGI(TAG, "BME68X initialized with profile cycling, operational: %s", m_operational ? "true" : "false"); return m_operational; } /// @brief /// @param data /// @return returns true when measurement cycle is complete (may take longer than 10s) bool Bme68x::read(struct BME_DATA * data) { static uint32_t read_call_count = 0; static uint32_t last_log_time = 0; static uint64_t cycle_start_time = 0; static bool index_9_detected = false; read_call_count++; uint64_t now_ms = esp_timer_get_time() / 1000; // milliseconds uint32_t now_sec = now_ms / 1000; // seconds // Log every 10 seconds to show we're being called if (now_sec - last_log_time >= 10) { ESP_LOGI(TAG, "Read called %u times in last 10s, current profile: %d", read_call_count - (last_log_time * 100), m_current_profile); last_log_time = now_sec; } // Calculate expected cycle duration for current profile const HeaterProfile& profile = profiles[m_current_profile]; uint32_t total_multipliers = 0; for (int i = 0; i < 10; i++) { total_multipliers += profile.mul_prof[i]; } uint32_t expected_cycle_duration = total_multipliers * 99; // milliseconds // Initialize cycle start time on first call after profile switch if (cycle_start_time == 0) { cycle_start_time = now_ms; index_9_detected = false; ESP_LOGI(TAG, "Starting profile %d cycle, expected duration: %u ms", m_current_profile, expected_cycle_duration); } MyLibs::bme68xData bdata; uint8_t left; bool ret_val = false; uint8_t num_fields = m_sensor->fetchData(); if(num_fields > 0) { ESP_LOGD(TAG, "fetchData() returned %d fields", num_fields); do { left = m_sensor->getData(bdata); if (bdata.status & BME68X_NEW_DATA_MSK) { int ix = _min(bdata.gas_index, 9); // Check what status flags we have bool has_new_data = (bdata.status & BME68X_NEW_DATA_MSK) != 0; bool has_heat_stab = (bdata.status & BME68X_HEAT_STAB_MSK) != 0; bool has_gasm_valid = (bdata.status & BME68X_GASM_VALID_MSK) != 0; ESP_LOGD(TAG, "Data: profile=%d, index=%d, status=0x%02X (new:%d, heat:%d, gas:%d), resistance=%0.1f", m_current_profile, ix, bdata.status, has_new_data, has_heat_stab, has_gasm_valid, bdata.gas_resistance); if (bdata.status == NEW_GAS_MEAS) { // Full valid measurement data->measurement_bitmask |= (1 << ix); data->current_profile = m_current_profile; ESP_LOGI(TAG, "VALID measurement: profile=%d, index=%d, resistance=%0.1f", m_current_profile, ix, bdata.gas_resistance); if(ix == 0) data->humidity = bdata.humidity; else if(ix == 9) { ESP_LOGI(TAG, "Index 9 detected for profile %d", m_current_profile); index_9_detected = true; } data->measurement[ix].resistance = bdata.gas_resistance; } else if (has_new_data && has_heat_stab) { // Accept measurements with heat stability even if gas measurement isn't fully valid yet //ESP_LOGW(TAG, "Accepting heat-stable measurement: profile=%d, index=%d", m_current_profile, ix); data->measurement_bitmask |= (1 << ix); data->current_profile = m_current_profile; if(ix == 0) data->humidity = bdata.humidity; else if(ix == 9) { //ESP_LOGI(TAG, "Index 9 detected (heat stable) for profile %d", m_current_profile); index_9_detected = true; } data->measurement[ix].resistance = bdata.gas_resistance; } } } while(left > 0); } // Check if cycle is complete (Index 9 detected AND enough time has passed) if (index_9_detected) { uint64_t elapsed_time = now_ms - cycle_start_time; if (elapsed_time >= expected_cycle_duration) { ESP_LOGI(TAG, "Profile %d cycle complete after %llu ms (expected %u ms)", m_current_profile, elapsed_time, expected_cycle_duration); ret_val = true; cycle_start_time = 0; // Reset for next profile index_9_detected = false; switchToNextProfile(); } else { ESP_LOGD(TAG, "Profile %d: waiting for heating completion (%u ms remaining)", m_current_profile, (uint32_t)(expected_cycle_duration - elapsed_time)); } } return ret_val; }