Merge pull request #84 from miguel5612/codex/verificar-resolución-del-error--ovf--en-sensor-mq-3

Fix overflow for CH4 calculations
This commit is contained in:
Miguel Angel Califa Urquiza 2025-06-04 18:57:29 -05:00 committed by GitHub
commit 6f9b00666e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 30 deletions

View File

@ -5,10 +5,12 @@ Este documento resume los problemas reportados en el repositorio y las solucione
## Abiertos
### #75 MQ-3 sensor and CH4 gas reading 'ovf' failure
**Estado:** abierto
**Estado:** resuelto en la rama `main`
El usuario reporta desbordamiento ("ovf") al utilizar valores muy altos en `setA` y `setB` para leer CH4 con un sensor MQ-3. La solución propuesta es revisar los valores utilizados en `setA` y `setB`, ya que `2*10^31` en C++ no corresponde a `2e31`. Se recomienda usar notación exponencial (`2e31`) o `pow(10,31)` y comprobar que los parámetros no excedan el rango de `float`.
**Actualización:** se añadieron validaciones de entrada, predicción de desbordamiento mediante logaritmos y verificación del resultado final para evitar valores "ovf" incluso con coeficientes muy altos. Las nuevas funciones limitan `setA` y `setB` y emplean cálculos en doble precisión.
### #74 possible error in the calculation formula for `_RS_Calc`
**Estado:** resuelto en la rama `work`

View File

@ -1,4 +1,6 @@
#include "MQUnifiedsensor.h"
#include <float.h>
#include <math.h>
#define retries 2
#define retry_interval 20
@ -21,11 +23,39 @@ void MQUnifiedsensor::init()
{
pinMode(_pin, INPUT);
}
static inline double safePow(double base, double exp){
if(exp == 0.0) return 1.0;
if(exp == 1.0) return base;
if(exp == 2.0) return base * base;
return pow(base, exp);
}
static inline bool willOverflow(double log_ppm){
static const double maxLog = log10((double)FLT_MAX);
static const double minLog = log10((double)FLT_MIN);
return (log_ppm > maxLog || log_ppm < minLog);
}
void MQUnifiedsensor::setA(float a) {
this->_a = a;
if(isinf(a) || isnan(a)) {
this->_a = 0;
} else if(a > MQ_MAX_A) {
this->_a = MQ_MAX_A;
} else if(a < -MQ_MAX_A) {
this->_a = -MQ_MAX_A;
} else {
this->_a = a;
}
}
void MQUnifiedsensor::setB(float b) {
this->_b = b;
if(isinf(b) || isnan(b)) {
this->_b = 0;
} else if(b > MQ_MAX_B) {
this->_b = MQ_MAX_B;
} else if(b < -MQ_MAX_B) {
this->_b = -MQ_MAX_B;
} else {
this->_b = b;
}
}
void MQUnifiedsensor::setR0(float R0) {
this->_R0 = R0;
@ -134,20 +164,23 @@ void MQUnifiedsensor::externalADCUpdate(float volt)
}
float MQUnifiedsensor::validateEcuation(float ratioInput)
{
//Serial.print("Ratio input: "); Serial.println(ratioInput);
//Serial.print("a: "); Serial.println(_a);
//Serial.print("b: "); Serial.println(_b);
//Usage of this function: Unit test on ALgorithmTester example;
if(_regressionMethod == 1) _PPM= _a*pow(ratioInput, _b);
else
{
// https://jayconsystems.com/blog/understanding-a-gas-sensor
double ppm_log = (log10(ratioInput)-_b)/_a; //Get ppm value in linear scale according to the the ratio value
_PPM = pow(10, ppm_log); //Convert ppm value to log scale
double ppm;
if(_regressionMethod == 1){
if(ratioInput <= 0 || _a == 0) return 0;
double logppm = log10((double)_a) + (double)_b * log10((double)ratioInput);
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
//Serial.println("Regression Method: "); Serial.println(_regressionMethod);
//Serial.println("Result: "); Serial.println(_PPM);
return _PPM;
else
{
if(ratioInput <= 0 || _a == 0) return 0;
double logppm = (log10((double)ratioInput)-(double)_b)/(double)_a;
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
if(isinf(ppm) || isnan(ppm)) ppm = FLT_MAX;
_PPM = (float)ppm;
return _PPM;
}
float MQUnifiedsensor::readSensor(bool isMQ303A, float correctionFactor, bool injected)
{
@ -162,14 +195,23 @@ float MQUnifiedsensor::readSensor(bool isMQ303A, float correctionFactor, bool in
if(!injected) _ratio = _RS_Calc / this->_R0; // Get ratio RS_gas/RS_air
_ratio += correctionFactor;
if(_ratio <= 0) _ratio = 0; //No negative values accepted or upper datasheet recommendation.
if(_regressionMethod == 1) _PPM= _a*pow(_ratio, _b); // <- Source excel analysis https://github.com/miguel5612/MQSensorsLib_Docs/tree/master/Internal_design_documents
else
{
// https://jayconsystems.com/blog/understanding-a-gas-sensor <- Source of linear equation
double ppm_log = (log10(_ratio)-_b)/_a; //Get ppm value in linear scale according to the the ratio value
_PPM = pow(10, ppm_log); //Convert ppm value to log scale
double ppm;
if(_regressionMethod == 1){
if(_ratio <= 0 || _a == 0) return 0;
double logppm = log10((double)_a) + (double)_b * log10((double)_ratio);
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
if(_PPM < 0) _PPM = 0; //No negative values accepted or upper datasheet recommendation.
else
{
if(_ratio <= 0 || _a == 0) return 0;
double logppm = (log10((double)_ratio)-(double)_b)/(double)_a;
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
if(ppm < 0) ppm = 0; //No negative values accepted or upper datasheet recommendation.
if(isinf(ppm) || isnan(ppm)) ppm = FLT_MAX;
_PPM = (float)ppm;
//if(_PPM > 10000) _PPM = 99999999; //No negative values accepted or upper datasheet recommendation.
return _PPM;
}
@ -180,14 +222,23 @@ float MQUnifiedsensor::readSensorR0Rs()
if(_RS_Calc < 0) _RS_Calc = 0; //No negative values accepted.
_ratio = this->_R0/_RS_Calc; // Get ratio RS_air/RS_gas <- INVERTED for MQ-131 issue 28 https://github.com/miguel5612/MQSensorsLib/issues/28
if(_ratio <= 0) _ratio = 0; //No negative values accepted or upper datasheet recommendation.
if(_regressionMethod == 1) _PPM= _a*pow(_ratio, _b); // <- Source excel analysis https://github.com/miguel5612/MQSensorsLib_Docs/tree/master/Internal_design_documents
else
{
// https://jayconsystems.com/blog/understanding-a-gas-sensor <- Source of linear equation
double ppm_log = (log10(_ratio)-_b)/_a; //Get ppm value in linear scale according to the the ratio value
_PPM = pow(10, ppm_log); //Convert ppm value to log scale
double ppm;
if(_regressionMethod == 1){
if(_ratio <= 0 || _a == 0) return 0;
double logppm = log10((double)_a) + (double)_b * log10((double)_ratio);
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
if(_PPM < 0) _PPM = 0; //No negative values accepted or upper datasheet recommendation.
else
{
if(_ratio <= 0 || _a == 0) return 0;
double logppm = (log10((double)_ratio)-(double)_b)/(double)_a;
if(willOverflow(logppm)) ppm = (logppm > 0) ? FLT_MAX : 0.0;
else ppm = safePow(10.0, logppm);
}
if(ppm < 0) ppm = 0; //No negative values accepted or upper datasheet recommendation.
if(isinf(ppm) || isnan(ppm)) ppm = FLT_MAX;
_PPM = (float)ppm;
//if(_PPM > 10000) _PPM = 99999999; //No negative values accepted or upper datasheet recommendation.
return _PPM;
}

View File

@ -3,6 +3,12 @@
#include <Arduino.h>
#include <stdint.h>
#include <float.h>
#include <math.h>
// Maximum coefficients allowed for the regression model
#define MQ_MAX_A 1e30
#define MQ_MAX_B 100.0
/***********************Software Related Macros************************************/