diff --git a/docs/source/Plugin/P145.rst b/docs/source/Plugin/P145.rst index 01011d9de..3a894e2ec 100644 --- a/docs/source/Plugin/P145.rst +++ b/docs/source/Plugin/P145.rst @@ -29,10 +29,12 @@ This plugin supports gas sensors for which the resistance depends on a gas conce The analog value of the sensors is determined by a resistance (Rsensor). The sensor is typically used in series with a load resistor (Rload). The voltage is over Rload is connected to the analog input of the ESP. A sensor and gas specific conversion formula is used to convert the measured voltage to a gas concentration. +Typical MQ-xxx sensor connection: + .. image:: P145_MQ-xxx-Diagram.jpg :alt: Basic circuit to connect MQ-xxx sensors -The plugin can be configured to select one of the predefined sensor types and tune the conversion parameters. +The plugin can be configured to select one of the predefined sensor types. Various parameters can be tuned to adapt the conversion. Basic algorithm ^^^^^^^^^^^^^^^ @@ -40,6 +42,8 @@ Basic algorithm The MQ-xxx series of sensors contain a resistor that depends on the concentration of certain gases in the environment. This relation is typically provided in the datasheets as a curve on a logarithmic plot. Figure below shows this relation for the MQ-3 sensor. In the indicated range the plot is close to linear. The plugin uses a conversion algoritm based upon a linear relation in the log-log plot. This results in two parameters parA and parB that determine this linear relation. +Example MQ-xxx sensor gas level to resistance graph: + .. image:: P145_SensorPlot.jpg :alt: Relation between gas level and sensor resistance (Rsensor/Rzero) @@ -60,6 +64,8 @@ Temperature and humidity compensation The MQ-xxx series of sensors is depending on the environment temperature and humidity. The datasheets often provide a graph that shows the relation between the Rsensor/Rzero ratio and environment temperature for various relative humidity levels. Figure below shows this relation for the MQ-3 sensor. From the plot it is clear that this relation is not linear. +Example MQ-xxx temperature and humidity influence on sensor resistance + .. image:: P145_TemHumDependency.jpg :alt: Relation between temperature & humidity versus sensor resistance (Rsensor/Rzero) @@ -107,29 +113,35 @@ Hardware Sensors can be bought as a component. The internet is full of development boards that contain the sensor with load resistor connected. These boards also provide a comparator that compares the output value with a preset value for a simple digital output. +Development board with MQ-7 CO sensor: + .. image:: P145_MQ_hardware.jpeg :alt: MQ-7 development board - -Development board with MQ-7 CO sensor +Schematics for development board: + .. image:: P145_MQxxx-sensor-board.jpg :alt: Development board schematics -Schematics for development board - Connections ^^^^^^^^^^^^ The sensor analog output is connected to an analog input on the ESP. The digital output provided on development boards is not used by the plugin. You can use the digital input plugin instead (P001). -See analog input plugin for more information about the available analog inputs for each ESP version. +The plugin uses the standard ESPeasy mechanism to select an analog input pin. Selection options depend on the type of ESP used. +See analog input plugin for more information about the available analog inputs for each of the supported ESP versions. Load resistor remarks ^^^^^^^^^^^^^^^^^^^^^ The manufactors of the MQ-xxx sensors provide the conversion plots given a set of environment conditions. This includes the supply voltage and a prescribed range for the load resistor Rload. Unfortunately most of the cheap development boards put a standard 1kOhm resistor on the board. For most sensors this value is far too low. The plugin can handle such values. -However such a low value for the load resistor decreases the range of the output signal and thus the accuracy of the measurent. It also uses the sensor in a range that is not supported by the manufator. -It is advised to replace this sensor by a value fitting the range advised by the manufactor. You can find these in the datasheets. +However, a too low value for the load resistor decreases the range of the output voltage signal and thus the accuracy of the measurent. It also uses the sensor in a range that is not supported by the manufator. +It is advised to replace this resistor by a value fitting the range advised by the manufactor. You can find the correct range in the datasheet for the sensor. + +Location of Rload on many of the cheap development boards: + +.. image:: P145_loadResistor.png + :alt: Load register location on sensor development boards Many ESP8266 boards use resistors to divide the analog input value so map the 1 Volt input range of the ESP to the 3.3 Volt supply range. These resistors are in parallel with the Rload on the sensor board. This shall be taken into account when determining the Rload value for the plugin. diff --git a/docs/source/Plugin/P145_loadResistor.png b/docs/source/Plugin/P145_loadResistor.png new file mode 100644 index 000000000..522e8e64b Binary files /dev/null and b/docs/source/Plugin/P145_loadResistor.png differ diff --git a/src/_P145_MQxxx.ino b/src/_P145_MQxxx.ino index 717b11f63..99e9acfe8 100644 --- a/src/_P145_MQxxx.ino +++ b/src/_P145_MQxxx.ino @@ -151,7 +151,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) # ifdef ESP32 // Analog input selection addRowLabel(F("Analog Pin")); - addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch_HallEffect, F("taskdevicepin1"), CONFIG_PIN1); + addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch_HallEffect, F("taskdevicepin1"), CONFIG_PIN_AIN); # endif // ifdef ESP32 addFormFloatNumberBox(F("Load Resistance"), F("plugin_145_RLOAD"), P145_PCONFIG_RLOAD, 0.0f, 10e6f, 2U); @@ -175,10 +175,13 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) bool compensate = P145_PCONFIG_FLAGS & 0x0001; bool calibrate = (P145_PCONFIG_FLAGS >> 1) & 0x0001; addFormCheckBox(F("Enable automatic calibration"), F("plugin_145_enable_calibrarion"), calibrate); - addFormCheckBox(F("Enable temp/humid compensation"), F("plugin_145_enable_compensation"), compensate); - addFormNote(F("If this is enabled, the Temperature and Humidity values below need to be configured.")); - //if (compensate) + //addFormCheckBox(F("Enable temp/humid compensation"), F("plugin_145_enable_compensation"), compensate); + addFormSelector_YesNo(F("Enable temp/humid compensation"), F("plugin_145_enable_compensation"), compensate, true); + // Above selector will fore reloading the page and thus updating the compensate flag + // Show the compensation details only when compensation is enabled + if (compensate) { + addFormNote(F("If compensation is enabled, the Temperature and Humidity values below need to be configured.")); // temperature addRowLabel(F("Temperature")); addTaskSelect(F("plugin_145_temperature_task"), P145_PCONFIG_TEMP_TASK); @@ -210,7 +213,8 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) P145_PCONFIG_RLOAD = getFormItemFloat(F("plugin_145_RLOAD")); P145_PCONFIG_RZERO = getFormItemFloat(F("plugin_145_RZERO")); P145_PCONFIG_REF = getFormItemFloat(F("plugin_145_REFLEVEL")); - bool compensate = isFormItemChecked(F("plugin_145_enable_compensation") ); + //bool compensate = isFormItemChecked(F("plugin_145_enable_compensation") ); + bool compensate = (getFormItemInt(F("plugin_145_enable_compensation")) == 1); bool calibrate = isFormItemChecked(F("plugin_145_enable_calibrarion") ); P145_PCONFIG_FLAGS = compensate + (calibrate << 1); P145_PCONFIG_TEMP_TASK = getFormItemInt(F("plugin_145_temperature_task")); @@ -263,7 +267,8 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) { float temperature = 20.0f; // A reasonable value in case temperature source task is invalid float humidity = 60.0f; // A reasonable value in case tumidity source task is invalid - if (validTaskIndex(P145_PCONFIG_TEMP_TASK) && validTaskIndex(P145_PCONFIG_HUM_TASK)) + bool compensate = P145_PCONFIG_FLAGS & 0x0001; + if (compensate && validTaskIndex(P145_PCONFIG_TEMP_TASK) && validTaskIndex(P145_PCONFIG_HUM_TASK)) { // we're checking a var from another task, so calculate that basevar temperature = UserVar[P145_PCONFIG_TEMP_TASK * VARS_PER_TASK + P145_PCONFIG_TEMP_VAL]; // in degrees C diff --git a/src/src/PluginStructs/P145_data_struct.cpp b/src/src/PluginStructs/P145_data_struct.cpp index 8f5799bec..dbb10e9a5 100644 --- a/src/src/PluginStructs/P145_data_struct.cpp +++ b/src/src/PluginStructs/P145_data_struct.cpp @@ -270,7 +270,7 @@ float P145_data_struct::getPPM(float rSensor) return (sensordef.para * powf((rSensor/rzero), -sensordef.parb)); break; case p145AlgB: // Miquel5612 Exponential - return (sensordef.para *powf((rSensor/rzero), sensordef.parb)); + return (sensordef.para * powf((rSensor/rzero), sensordef.parb)); break; case p145AlgC: // Miquel5612 Linear return (powf(10.0f, (log10f(rSensor/rzero)-sensordef.parb)/sensordef.para)); @@ -294,10 +294,10 @@ 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) { case p145AlgA: - float c; // Temperature & humidity correction factor if (temperature < 20.0f) { c = sensordef.cora * temperature * temperature - sensordef.corb * temperature + sensordef.corc - (humidity - 33.0f) * sensordef.cord; @@ -306,7 +306,6 @@ float P145_data_struct::getCorrectedPPM(float rSensor, float temperature, float { c = sensordef.core * temperature + sensordef.corf * humidity + sensordef.corg; } - return (sensordef.para * pow(((rSensor / c) / rzero), -sensordef.parb)); case p145AlgB: // TODO Still missing a formula to correct for temp/hum when applying this algorithm break; @@ -317,7 +316,7 @@ float P145_data_struct::getCorrectedPPM(float rSensor, float temperature, float // Do nothing, return a default value at the end break; } - return 0.0f; // Default value should never be returned + return getPPM(rSensor/c); // Default value should never be returned } /*****************************************************************************/ @@ -429,6 +428,8 @@ float P145_data_struct::readValue(float temperature, float humidity) addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: RS= "), rSensor)); // Calculated sensor resistance Rsensor #ifdef P145_DEBUG addLog(LOG_LEVEL_INFO, concat(F("MQ-xx: Ref= "), refLevel)); // Reference level for calibration + 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: algorithm= "), algorithm)); // Conversion algorithm diff --git a/src/src/PluginStructs/P145_data_struct.h b/src/src/PluginStructs/P145_data_struct.h index 56a9e788f..205b76eab 100644 --- a/src/src/PluginStructs/P145_data_struct.h +++ b/src/src/PluginStructs/P145_data_struct.h @@ -38,19 +38,19 @@ enum P145_algorithm // Members are preferrably sorted by memory alignment struct P145_SENSORDEF { - float cleanRatio; // Rs/R0 ratio in clean air - float para; // PARA scaling factor value - float parb; // PARB exponent value - float cora; // CORA - float corb; // CORB - float corc; // CORC - float cord; // CORD - float core; // CORE - float corf; // CORF - float corg; // CORG + float cleanRatio; // Rs/R0 ratio in clean air + float para; // PARA scaling factor value + float parb; // PARB exponent value + float cora; // CORA + float corb; // CORB + float corc; // CORC + float cord; // CORD + float core; // CORE + float corf; // CORF + float corg; // CORG P145_algorithm alg; // Preferred/tuned algorithm - char name[8]; // Sensor type name - char gas[8]; // Measured gas concentration + char name[8]; // Sensor type name + char gas[8]; // Measured gas concentration }; struct P145_data_struct : public PluginTaskData_base @@ -65,12 +65,12 @@ struct P145_data_struct : public PluginTaskData_base uint ovs_cnt = 0; // Oversampling algorithm sample counter float last_ain = 0.0; // Oversampling algorithm last measured analog input value /* Calibration static data */ - ulong last_cal = 0; // Last calibration timestamp - float rcal = 0.0; // Rcal, calibration resistance [Ohm] + ulong last_cal = 0U; // Last calibration timestamp + float rcal = 0.0f; // Rcal, calibration resistance [Ohm] /* Sensor value conversion parameters */ - float rload = 0.0; // Rload, load resistor [Ohm] - float rzero = 0.0; // R0, reference resistance [Ohm] - float refLevel = 0.0; // Reference level for calibration [ppm] + float rload = 0.0f; // Rload, load resistor [Ohm] + float rzero = 0.0f; // R0, reference resistance [Ohm] + float refLevel = 0.0f; // Reference level for calibration [ppm] /* Sensor type & user options */ bool compensation = false; // Use temperature compensation bool calibration = false; // Perform auto calibration