From 7bb92fbfaad7454692d4b181e3db9670a23a4e4e Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Wed, 22 Feb 2023 23:11:23 +0100 Subject: [PATCH] P145 plugin updates & refactoring --- src/_P145_MQxxx.ino | 39 ++- src/src/PluginStructs/P145_data_struct.cpp | 316 +++++++++++---------- src/src/PluginStructs/P145_data_struct.h | 1 + 3 files changed, 195 insertions(+), 161 deletions(-) diff --git a/src/_P145_MQxxx.ino b/src/_P145_MQxxx.ino index 76b735664..c4ba12a03 100644 --- a/src/_P145_MQxxx.ino +++ b/src/_P145_MQxxx.ino @@ -59,12 +59,12 @@ // P145_PCONFIG_RLOAD PCONFIG_FLOAT(0) RLOAD [Ohm] // P145_PCONFIG_RZERO PCONFIG_FLOAT(1) RZERO [Ohm] // P145_PCONFIG_REF PCONFIG_FLOAT(2) REF. level [ppm] -// P145_PCONFIG_FLAGS PCONFIG(0) Enable compensation & Enable Calibration & use low Vcc -// P145_PCONFIG_TEMP_TASK PCONFIG(1) Temperature compensation task -// P145_PCONFIG_TEMP_VAL PCONFIG(2) Temperature compensation value -// P145_PCONFIG_HUM_TASK PCONFIG(3) Humidity compensation task -// P145_PCONFIG_HUM_VAL PCONFIG(4) Humidity compensation value -// P145_PCONFIG_SENSORT PCONFIG(5) Sensor type +// P145_PCONFIG_FLAGS PCONFIG(0) Enable compensation & Enable Calibration & use low Vcc +// P145_PCONFIG_TEMP_TASK PCONFIG(1) Temperature compensation task +// P145_PCONFIG_TEMP_VAL PCONFIG(2) Temperature compensation value +// P145_PCONFIG_HUM_TASK PCONFIG(3) Humidity compensation task +// P145_PCONFIG_HUM_VAL PCONFIG(4) Humidity compensation value +// P145_PCONFIG_SENSORT PCONFIG(5) Sensor type //####################################################################################################### #include "_Plugin_Helper.h" @@ -119,6 +119,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) switch (function) { + // Make the plugin known with its options case PLUGIN_DEVICE_ADD: { Device[++deviceCount].Number = PLUGIN_ID_145; @@ -135,18 +136,39 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Return the DEVICENAME for this plugin case PLUGIN_GET_DEVICENAME: { string = F(PLUGIN_NAME_145); break; } + // Setup the value names for the task case PLUGIN_GET_DEVICEVALUENAMES: { strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_145)); break; } + // Add custom GPIO description on device overview page + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = F("AIN: "); +# ifdef ESP32 + string += formatGpioLabel(P145_CONFIG_PIN_AIN, false); +#else + string += F("ADC (TOUT)"); +#endif + if (validGpio(P145_CONFIG_PIN_HEATER)) + { + string += F("
HEAT: "); + string += formatGpioLabel(P145_CONFIG_PIN_HEATER, false); + } + success = true; + break; + } + + // Setup the web form for the task case PLUGIN_WEBFORM_LOAD: { bool compensate = P145_PCONFIG_FLAGS & 0x0001; // Compensation enable flag @@ -226,6 +248,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Save setting from web form for the task case PLUGIN_WEBFORM_SAVE: { P145_PCONFIG_SENSORT = getFormItemInt(F(P145_GUID_TYPE)); @@ -256,6 +279,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Initialize the task case PLUGIN_INIT: { P145_data_struct *P145_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -275,6 +299,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Execute at 10Hz case PLUGIN_TEN_PER_SECOND: { P145_data_struct *P145_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -285,6 +310,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Read fresh data from the task case PLUGIN_READ: { P145_data_struct *P145_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -305,6 +331,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) break; } + // Execute each second case PLUGIN_ONCE_A_SECOND: { P145_data_struct *P145_data = static_cast(getPluginTaskData(event->TaskIndex)); diff --git a/src/src/PluginStructs/P145_data_struct.cpp b/src/src/PluginStructs/P145_data_struct.cpp index 58907ea69..0bcf74a43 100644 --- a/src/src/PluginStructs/P145_data_struct.cpp +++ b/src/src/PluginStructs/P145_data_struct.cpp @@ -21,24 +21,24 @@ // Below the defines used to represent these enums // Gas type identifiers used to condense the sensorDefs table to fixed format -#define GAS_USER 0 -#define GAS_CO2 1 -#define GAS_H2 2 -#define GAS_ALCOHOL 3 -#define GAS_CH4 4 -#define GAS_LPG 5 -#define GAS_CO 6 +#define P145_GASUSER 0 +#define P145_GASCO2 1 +#define P145_GASH2 2 +#define P145_GASALCOHOL 3 +#define P145_GASCH4 4 +#define P145_GASLPG 5 +#define P145_GASCO 6 // Sensor type identifier used to condense the SensorDefs table to fixed format -#define SENS_USER 0 -#define SENS_MQ135 1 -#define SENS_MQ2 2 -#define SENS_MQ3 3 -#define SENS_MQ4 4 -#define SENS_MQ5 5 -#define SENS_MQ6 6 -#define SENS_MQ7 7 -#define SENS_MQ8 8 +#define P145_SENSUSER 0 +#define P145_SENSMQ135 1 +#define P145_SENSMQ2 2 +#define P145_SENSMQ3 3 +#define P145_SENSMQ4 4 +#define P145_SENSMQ5 5 +#define P145_SENSMQ6 6 +#define P145_SENSMQ7 7 +#define P145_SENSMQ8 8 @@ -62,8 +62,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgNone, // preferred/tuned algorithm - SENS_USER, // Name - GAS_USER, // gas + P145_SENSUSER, // Name + P145_GASUSER, // gas }, // *** MQ-135 - CO2 *** { @@ -78,8 +78,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = -0.001923077f,// CORF 1.130128205f, // CORG p145AlgA, // preferred/tuned algorithm - SENS_MQ135, // Name - GAS_CO2, // gas + P145_SENSMQ135, // Name + P145_GASCO2, // gas }, // *** MQ-2 - H2 *** { @@ -94,8 +94,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ2, // Name - GAS_H2, // gas + P145_SENSMQ2, // Name + P145_GASH2, // gas }, // *** MQ-3 - alcohol *** { @@ -110,8 +110,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ3, // Name - GAS_ALCOHOL, // gas + P145_SENSMQ3, // Name + P145_GASALCOHOL, // gas }, // *** MQ-4 - CH4 *** { @@ -126,8 +126,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ4, // Name - GAS_CH4, // gas + P145_SENSMQ4, // Name + P145_GASCH4, // gas }, // *** MQ-5 - H2 *** { @@ -142,8 +142,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ5, // Name - GAS_CH4, // gas + P145_SENSMQ5, // Name + P145_GASCH4, // gas }, // *** MQ-5 - LPG *** { @@ -158,8 +158,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ5, // Name - GAS_LPG, // gas + P145_SENSMQ5, // Name + P145_GASLPG, // gas }, // *** MQ-6 - LPG *** { @@ -174,8 +174,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ6, // Name - GAS_LPG, // gas + P145_SENSMQ6, // Name + P145_GASLPG, // gas }, // *** MQ-7 - CO *** { @@ -190,8 +190,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ7, // Name - GAS_CO, // gas + P145_SENSMQ7, // Name + P145_GASCO, // gas }, // *** MQ-8 - H2 *** { @@ -206,8 +206,8 @@ const struct P145_SENSORDEF sensorDefs[] PROGMEM = 0.0f, // CORF 0.0f, // CORG p145AlgB, // preferred/tuned algorithm - SENS_MQ8, // Name - GAS_H2, // gas + P145_SENSMQ8, // Name + P145_GASH2, // gas } }; /// @brief The number of types stored in the sensorDefs[] table @@ -223,47 +223,6 @@ constexpr const int nbrOfTypes = (int)(sizeof(sensorDefs) / sizeof(struct P145_S #define HEATER_OFF_TIME (60*1000) #define HEATER_MEAS_TIME (30*1000) -/*****************************************************************************/ -/*! -@brief Perform the calibration algorithm -@param[in] currentRcal Value for Rzero determined with current measurement -@return Void -@note Collect the highest Rzero during the calibration period - At the end of the period take mean value of current and calculated Rzero -*/ -/*****************************************************************************/ -void P145_data_struct::calibrate (float currentRcal) -{ - unsigned long now = millis(); - long time = timePassedSince(last_cal); - float lastRcal = cal_data; // Last calculated Rcal this calibration sequence - - if ((currentRcal > lastRcal) || lastRcal <= 0.0f) - { - cal_data = currentRcal; - } - const bool doit = (time > P145_CALIBRATION_INTERVAL) && (cal_data > 0.0f); - if (doit) - { - rzero = cal_data; // Update Rzero as determined by calibration - cal_data = 0.0f; // Restart calibration cycle with no value - last_cal = now; // Remember calibration moment - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) - { -#ifdef P145_DEBUG - // Calculated Rzero if calibration concentration is applied - // Rcal as calculated previous sample - addLog(LOG_LEVEL_INFO, concat(concat(F("MQ-xx: Calibration with Rcal = "), currentRcal), concat(F(" Rlast = "), lastRcal))); -#endif - if (doit) - { - addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: ***Calibrating*** Rzero = "), cal_data)); - } - } -} - /*****************************************************************************/ /*! @brief Get the resistance of the sensor, ie. the property to be measured @@ -347,21 +306,8 @@ float P145_data_struct::getRZero(float rSensor) const /*****************************************************************************/ float P145_data_struct::getCorrectedRZero(float rSensor, float temperature, float humidity) const { - float c; // Correction factor - - if (sensordef.cora <= 0.0f) - { - return (getRZero(rSensor)); - } - else if (temperature < 20.0f) //TODO this is an ugly constant associated with the MQ-135 sensor only - { - c = sensordef.cora * temperature * temperature - sensordef.corb * temperature + sensordef.corc - (humidity - 33.0f) * sensordef.cord; - } - else - { - c = sensordef.core * temperature + sensordef.corf * humidity + sensordef.corg; - } - return (rSensor / c) * powf((refLevel / sensordef.para), (1.0f / sensordef.parb)); + float c = getTempHumCorrection(temperature, humidity); + return getRZero(rSensor/c); } /*****************************************************************************/ @@ -407,31 +353,48 @@ float P145_data_struct::getPPM(float rSensor) /*****************************************************************************/ float P145_data_struct::getCorrectedPPM(float rSensor, float temperature, float humidity) { - float c = 1.0f; // Correction factor - switch (algorithm) + float c = getTempHumCorrection(temperature, humidity); + return getPPM(rSensor/c); +} + +/*****************************************************************************/ +/*! +@brief Get the correction factor for temperature & humidity correction +@param[in] temperature The ambient air temperature +@param[in] humidity The relative humidity +@return The correction factor for Rzero to compensate temperature & humidity +@note Datasheet suggests temp & hum relation to Rzero is a multiplication factor c + here we try to calculate this factor. +*/ +/*****************************************************************************/ +float P145_data_struct::getTempHumCorrection(float temperature, float humidity) const +{ + float c = 1.0f; // Correction factor, default no correction, factor is 1.0 + if (compensation) { - case p145AlgA: - if (temperature < 20.0f) - { - c = sensordef.cora * temperature * temperature - sensordef.corb * temperature + sensordef.corc - (humidity - 33.0f) * sensordef.cord; - } - else - { - c = sensordef.core * temperature + sensordef.corf * humidity + sensordef.corg; - } - break; - case p145AlgB: - case p145AlgC: - // Assume a 2nd order polynomial for temperature and 1st order poynomial for humidit - // A + B*t + C*t*t + D*h - c = (((sensordef.corc * temperature ) + sensordef.corb) * temperature) + sensordef.cora + (sensordef.cord * humidity); - break; - case p145AlgNone: - // no correction - break; - default: - // No correction - c = 1.0f; + switch (algorithm) + { + case p145AlgA: + if (temperature < 20.0f) + { + c = sensordef.cora * temperature * temperature - sensordef.corb * temperature + sensordef.corc - (humidity - 33.0f) * sensordef.cord; + } + else + { + c = sensordef.core * temperature + sensordef.corf * humidity + sensordef.corg; + } + break; + case p145AlgB: + case p145AlgC: + // Assume a 2nd order polynomial for temperature and 1st order poynomial for humidit + // A + B*t + C*t*t + D*h + c = (((sensordef.corc * temperature ) + sensordef.corb) * temperature) + sensordef.cora + (sensordef.cord * humidity); + break; + case p145AlgNone: + default: + // No correction + c = 1.0f; + } } if (c <=0.0f) // In case no or wrong correction factors are specified disable correction (multiplier = 1.0) { @@ -440,7 +403,7 @@ float P145_data_struct::getCorrectedPPM(float rSensor, float temperature, float #ifdef P145_DEBUG addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: correction= "), c)); #endif - return getPPM(rSensor/c); // Default value should never be returned + return c; } /*****************************************************************************/ @@ -487,8 +450,8 @@ float P145_data_struct::getAnalogValue() /*****************************************************************************/ void P145_data_struct::setSensorData(int stype, bool comp, bool cal, bool vcclow, float load, float zero, float ref) { - /* Each MQ-xxx sensor comes with its own set cof constants */ - /* Copy the correct set from program meory space */ + /* Each MQ-xxx sensor comes with its own set of constants */ + /* Copy the correct set from program meory space */ if ((stype != sensorType) && (stype < nbrOfTypes) && (stype >= 0)) { memcpy_P(&sensordef, &sensorDefs[stype], sizeof(struct P145_SENSORDEF)); @@ -501,13 +464,15 @@ void P145_data_struct::setSensorData(int stype, bool comp, bool cal, bool vcclow rload = load; // Rload, load resistor [Ohm] rzero = zero; // R0, reference resistance [Ohm] refLevel = ref; // Reference level for calibration [ppm] + rcal_act = 0.0f; // Reset Rzero estimation + cal_data = 0.0f; // Reset autocalibration } /*****************************************************************************/ /*! @brief Set the connection pins for the sensor -@param[in] analogPin Analog pin for the analog sensor input value -@param[in] heaterPin Ootput pin for heater control +@param[in] aPin Analog pin for the analog sensor input value +@param[in] hPin Ootput pin for heater control @note These values must be set before the plugin can measure the level They are determined by the plugin configuration /*****************************************************************************/ @@ -535,7 +500,9 @@ float P145_data_struct::readValue(float temperature, float humidity) float ain = 0.0f; // Analog measured and filtered data float rSensor = 0.0f; // Sensor resistance Rs float value = 0.0f; // Return value - +#ifdef P145_DEBUG + uint ovs = 0; // Oversampling count (for debugging) +#endif #ifdef P145_TEST static float injector = 50.0f; static float injstep = 50.0f; @@ -557,27 +524,24 @@ float P145_data_struct::readValue(float temperature, float humidity) else { ain = getAnalogValue(); // Use acually being measured value +#ifdef P145_DEBUG + ovs = ovs_cnt; +#endif resetOversampling(); // Reset the oversampling variables. } #endif - rSensor = getResistance(ain); // Convert to Rsensor value - if (compensation) // Check if temperature/Humidity compensation is selected + if (ain > 0.0f) // Skip unsuccesful measurements { - value = getCorrectedPPM(rSensor, temperature, humidity); - rcal_act = getCorrectedRZero(rSensor, temperature, humidity); - } - else - { - value = getPPM(rSensor); - rcal_act = getRZero(rSensor); - } + rSensor = getResistance(ain); // Convert to Rsensor value + value = getCorrectedPPM(rSensor, temperature, humidity); + rcal_act = getCorrectedRZero(rSensor, temperature, humidity); - // Perform calibration if functionality is enabled - if (calibration && (rcal_act < 1.0e6f)) - { + // Perform calibration if functionality is enabled + if (calibration && (rcal_act < 1.0e6f)) + { calibrate(rcal_act); + } } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { #ifdef P145_DEBUG @@ -593,7 +557,7 @@ float P145_data_struct::readValue(float temperature, float humidity) addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: Temp= "), temperature)); // Temperature for compensation algorithm addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: Hum= "), humidity)); // Humidity for compensation algorithm addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: ain= "), ain)); // Measured analog input value - addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: ovs= "), ovs_cnt)); // Oversampling count + addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: ovs= "), ovs)); // Oversampling count addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: algorithm= "), algorithm)); // Conversion algorithm if (calibration) { @@ -718,6 +682,7 @@ void P145_data_struct::dump() const addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: CORF "), sensordef.corf)); addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: CORG "), sensordef.corg)); addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: rzero "), rzero)); + addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: rcal "), rcal_act)); addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: analog PIN: "), analogPin)); addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: heater PIN: "), heaterPin)); } @@ -736,16 +701,16 @@ const String P145_data_struct::getTypeName(int stype) { switch (sensorDefs[stype].name) { - case SENS_MQ135: return F("MQ135"); - case SENS_MQ2: return F("MQ-2"); - case SENS_MQ3: return F("MQ-3"); - case SENS_MQ4: return F("MQ-4"); - case SENS_MQ5: return F("MQ-5"); - case SENS_MQ6: return F("MQ-6"); - case SENS_MQ7: return F("MQ-7"); - case SENS_MQ8: return F("MQ-8"); - case SENS_USER: return F("user"); - default: return F("invalid"); + case P145_SENSMQ135: return F("MQ135"); + case P145_SENSMQ2: return F("MQ-2"); + case P145_SENSMQ3: return F("MQ-3"); + case P145_SENSMQ4: return F("MQ-4"); + case P145_SENSMQ5: return F("MQ-5"); + case P145_SENSMQ6: return F("MQ-6"); + case P145_SENSMQ7: return F("MQ-7"); + case P145_SENSMQ8: return F("MQ-8"); + case P145_SENSUSER: return F("user"); + default: return F("invalid"); } } @@ -761,14 +726,14 @@ const String P145_data_struct::getGasName(int stype) { switch (sensorDefs[stype].gas) { - case GAS_USER: return F("user"); - case GAS_CH4: return F("CH4"); - case GAS_CO2: return F("CO2"); - case GAS_CO: return F("CO"); - case GAS_H2: return F("H2"); - case GAS_LPG: return F("LPG"); - case GAS_ALCOHOL: return F("alcohol"); - default: return F("invalid"); + case P145_GASUSER: return F("user"); + case P145_GASCH4: return F("CH4"); + case P145_GASCO2: return F("CO2"); + case P145_GASCO: return F("CO"); + case P145_GASH2: return F("H2"); + case P145_GASLPG: return F("LPG"); + case P145_GASALCOHOL: return F("alcohol"); + default: return F("invalid"); } } @@ -783,6 +748,47 @@ int P145_data_struct::getNbrOfTypes() return nbrOfTypes; } +/*****************************************************************************/ +/*! +@brief Perform the calibration algorithm +@param[in] currentRcal Value for Rzero determined with current measurement +@return Void +@note Collect the highest Rzero during the calibration period + At the end of the period use it to set the Rzero for next calculations +*/ +/*****************************************************************************/ +void P145_data_struct::calibrate (float currentRcal) +{ + unsigned long now = millis(); + long time = timePassedSince(last_cal); + float lastRcal = cal_data; // Last calculated Rcal this calibration sequence + + if (currentRcal > lastRcal) + { + cal_data = currentRcal; // Remember highest estimated Rzero during calibration period + } + const bool doit = (time > P145_CALIBRATION_INTERVAL) && (cal_data > 0.0f); + if (doit) + { + rzero = cal_data; // Update Rzero as determined by calibration + cal_data = 0.0f; // Restart calibration cycle with no value + last_cal = now; // Remember calibration moment + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { +#ifdef P145_DEBUG + // Calculated Rzero if calibration concentration is applied + // Rcal as calculated previous sample + addLog(LOG_LEVEL_INFO, concat(concat(F("MQ-xx: Calibration with Rcal = "), currentRcal), concat(F(" Rlast = "), lastRcal))); +#endif + if (doit) + { + addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: ***Calibrating*** Rzero = "), cal_data)); + } + } +} + /**************************************************************************/ /* @brief Evaluate the heater control algorithm @return void diff --git a/src/src/PluginStructs/P145_data_struct.h b/src/src/PluginStructs/P145_data_struct.h index 6fd62be9e..42f29e6ad 100644 --- a/src/src/PluginStructs/P145_data_struct.h +++ b/src/src/PluginStructs/P145_data_struct.h @@ -117,6 +117,7 @@ struct P145_data_struct : public PluginTaskData_base float getAnalogValue(); float getResistance(float val) const; float getRZero(float rSensor) const; + float getTempHumCorrection(float temperature, float humidity) const; float getCorrectedRZero(float rSensor, float temperature, float humidity) const; float getPPM(float rSensor); float getCorrectedPPM(float rSensor, float temperature, float humidity);