Added support for proper smell profiles (8 0f them)

This commit is contained in:
RZ_MINIX\rober 2025-09-27 18:08:03 -07:00
parent 0b2fb3feea
commit 58308ef7fb
3 changed files with 338 additions and 24 deletions

View File

@ -3,68 +3,365 @@
#include "Bme68x.h" #include "Bme68x.h"
static const char *TAG = "Bme68x"; static const char *TAG = "Bme68x";
#define MEAS_DUR 140
#define NEW_GAS_MEAS (BME68X_GASM_VALID_MSK | BME68X_HEAT_STAB_MSK | BME68X_NEW_DATA_MSK) #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) Bme68x::Bme68x(TwoWire & bus) : m_bus(bus)
{ {
m_sensor = new MyLibs::Bme68x(); 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() bool Bme68x::init()
{ {
m_sensor->begin(0x77, m_bus); m_sensor->begin(0x77, m_bus);
m_sensor->setTPH(); m_sensor->setTPH();
uint16_t tempProf[10] = { 320, 100, 100, 100, 200, 200, 200, 320, 320, 320 }; // Start with profile 0
/* Multiplier to the shared heater duration */ switchToProfile(0);
uint16_t mulProf[10] = { 5, 2, 10, 30, 5, 5, 5, 5, 5, 5 };
/* Shared heating duration in milliseconds */
uint16_t sharedHeatrDur = MEAS_DUR - (m_sensor->getMeasDur(BME68X_PARALLEL_MODE) / 1000);
m_sensor->setHeaterProf(tempProf, mulProf, sharedHeatrDur, 10);
m_sensor->setOpMode(BME68X_PARALLEL_MODE);
m_operational = m_sensor->checkStatus() == BME68X_OK; m_operational = m_sensor->checkStatus() == BME68X_OK;
ESP_LOGI(TAG, "BME68X initialized with profile cycling, operational: %s", m_operational ? "true" : "false");
return m_operational; return m_operational;
} }
/// @brief /// @brief
/// @param data /// @param data
/// @return returns true when index 9 is read /// @return returns true when measurement cycle is complete (may take longer than 10s)
bool Bme68x::read(struct BME_DATA * data) 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; MyLibs::bme68xData bdata;
uint8_t left; uint8_t left;
bool ret_val = false; bool ret_val = false;
if(m_sensor->fetchData() > 0) uint8_t num_fields = m_sensor->fetchData();
if(num_fields > 0)
{ {
ESP_LOGD(TAG, "fetchData() returned %d fields", num_fields);
do do
{ {
left = m_sensor->getData(bdata); left = m_sensor->getData(bdata);
if (bdata.status == NEW_GAS_MEAS) if (bdata.status & BME68X_NEW_DATA_MSK)
{ {
int ix = _min(bdata.gas_index, 9); 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->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) if(ix == 0)
data->humidity = bdata.humidity; data->humidity = bdata.humidity;
else if(ix == 9) else if(ix == 9)
ret_val = true; {
ESP_LOGI(TAG, "Index 9 detected for profile %d", m_current_profile);
index_9_detected = true;
}
data->measurement[ix].resistance = bdata.gas_resistance; 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); } 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; return ret_val;
} }

View File

@ -16,6 +16,7 @@ struct BME_DATA
{ {
float humidity; float humidity;
uint16_t measurement_bitmask; uint16_t measurement_bitmask;
uint8_t current_profile; // Track which profile is active
struct GAS_MEASUREMENT measurement[10]; struct GAS_MEASUREMENT measurement[10];
}; };
@ -26,6 +27,20 @@ protected:
TwoWire & m_bus; TwoWire & m_bus;
bool m_operational = false; bool m_operational = false;
// Profile management
uint8_t m_current_profile = 0;
// Profile definitions
struct HeaterProfile {
uint16_t temp_prof[10];
uint16_t mul_prof[10];
};
static const HeaterProfile profiles[8];
void switchToProfile(uint8_t profile_index);
void switchToNextProfile();
public: public:
Bme68x(TwoWire & bus); Bme68x(TwoWire & bus);
bool init(); bool init();

View File

@ -175,7 +175,9 @@ void SensorService::postBme68xData(float pressure, float temp)
if(m_bme_data.measurement_bitmask & (1 << n)) if(m_bme_data.measurement_bitmask & (1 << n))
{ {
p[num_total].resistance = m_bme_data.measurement[n].resistance; p[num_total].resistance = m_bme_data.measurement[n].resistance;
p[num_total++].index = n; // Use index 100 + (profile * 10) + measurement_index
// Profile 0: indexes 100-109, Profile 1: indexes 110-119, etc.
p[num_total++].index = 100 + (m_bme_data.current_profile * 10) + n;
} }
} }
msg->num_data = num_total; msg->num_data = num_total;