367 lines
13 KiB
C++
367 lines
13 KiB
C++
/// © MiroZ 2024
|
|
#include <esp_log.h>
|
|
|
|
#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;
|
|
} |