Loading...

Sensores de gas MQ leer y calcular ppm

Pablo Durán
Comparte el post:

En este caso no se trata de un único sensor sino de toda una gama de ellos. Los que voy a usar son MQ2, MQ3, MQ4, MQ5, MQ7, MQ8, MQ9 y MQ135. También había adquirido un MQ6, pero este me llegó defectuoso.

Muchos de los sensores comparten la medición del mismo gas, pero en total mide los siguiente gases: propano, humo, benceno, acetona, tolueno, hidrogeno, metano, GPL, alcohol y monóxido de carbono al que nos dirigiremos de ahora en adelante como CO

Estos sensores no son de precisión ni profesionales. Son simplemente para uso didactico por lo que tampoco hay que prestarle mucha atención a los resultados que nos arroja.

Teoría

Cada sensor MQ es sensible a uno o varios tipos de gases. Si el sensor es sensible a varios tipos de gases al detectar uno en gran cantidad el resto de gases del mismo sensor también los leerá más alto, más adelante en este apartado explico por qué, así que teniendo toda una red de sensores podemos determinar de una forma más eficaz que gas tiene realmente una alta concentración.

Los sensores MQ contienen una resistencia variable en su interior la cual reacciona con X gas haciendo que pase más voltaje a través de la resistencia. Lo que mido realmente de los sensores es el voltaje de la salida analógica por lo que al detectar un aumento del voltaje sé que ha aumentado la concentración de uno de los gases que lee el sensor

Para leer este voltaje de salida usaremos los ADC como lo hemos hecho en la lectura de la veleta. El ADC funciona a 3.3 voltios mientras que los sensores MQ funcionan a 5 voltios. Según la ficha técnica del fabricante de los ADC modelo ADS1115 se puede conectar tanto a 3.3 como a 5 voltios y puede soportar hasta 2 voltios más de los que tiene de entrada. Por lo que si uso 3.3 voltios el conversor aguanta hasta 5.3 voltios.

Aviso: He usado voltaje de 3.3 en los ADC debido a que con una alimentación de 5 voltios generaba inestabilidad en los canales SDA y SCL por lo que podían dejar de funcionar las comunicaciones de todos los sensores con las Raspberry Pi.

Los sensores MQ tiene un pequeño quemador de hasta 0.8 amperios para catalizar las reacciones químicas ocurridas en el interior de su malla y obtener una buena lectura de estos. Debido al consumo eléctrico que tienen he tenido que usar un alimentador externo de 10A y 5V para poder suplir el consumo energético. El alimentador usado es el S-50-5 de Leadstar. Si intentas conectar todos los sensores MQ a la raspberry Pi puede que esta no te arranque o que se ropa debido a la cantidad de energía que se le está pidiendo.

Los sensores se han tenido encendidos entre 24 y 48 horas antes de su uso como recomienda el fabricante para eliminar posibles residuos que se hayan quedado adheridos a la resistencia generados en la producción de los sensores.

Cableado

Antes de cablear los sensores de gas tenemos que cablear los ADC para poder leer las señales analógicas.

  • Pin VDD: se conecta a un pin 3.3V de la placa.
  • Pin GND: se conecta a un pin GND de la placa y al negativo del alimentador para que los sensores MQ puedan tener un circuito cerrado y así recibir datos del pin analógico.
  • Pin SCL: se conecta al pin SCL de la placa.
  • Pin SCA: se conecta al pin SCA de la placa.

Como ya hemos visto en el caso del SHT31 al tener varios chips I2C del mismo modelo por lo que tenemos que cambiar la ruta de comunicación de estos para que cada uno pueda comunicarse independientemente con la Raspberry Pi. El ADC de la veleta usa la ruta 0x48 por lo que ahora usaremos la ruta 0x49 para el primer ADC que se consigue conectando el pin address del chip un cable con 3.3 voltios de entrada. Le asignamos la ruta 0x4a para el segundo ADC conectando su pin address al pin SDA.

Todos los sensores MQ tienen cuatro pines. Todos los pines VCC se conectar al alimentador externo de 5V y los GND al negativo del alimentador externo. En cuanto a los pines de salida digital se conecta cada uno a un pin GPIO de la placa y los pines de salida analógica cada uno a un canal diferente de entrada. Las conexiones son las siguientes:

MQ2 MQ3 MQ4 MQ5 MQ7 MQ8 MQ9 MQ135
Pin digital GPIO 14 GPIO 15 GPIO 23 GPIO 24 GPIO 25 GPIO 17 GPIO 27 GPIO 22
Pin analógico 0x49 canal 0 0x49 canal 1 0x49 canal 2 0x49 canal 3 0x4a canal 0 0x4a canal 1 0x4a canal 2 0x4a canal 3

Como el esque es algo complejo vamos a comentar para qué se ha usado cada color de los cables y así tener más claro el esquema:

  • Rojo: Es el positivo. Proporciona tanto la enegía de 3.3v como la de 5v. (No se juntan nunga los dos voltajes)
  • Negro: El negativo/tierra. Aquí si que están conectado tanto los ADC de 3.3v como los sensores MQ de 5v a la misma línea.
  • Azul: Es el canal SCL.
  • Naranja: Es el canal SDA.
  • Morado: Son los canales digitales de los sensores mq que lanza una señal de alarma cuando la concentración de gas es algo.

Una vez visto esto dejo la representación gráfica de como está conectado todo.

Cableado de los sensores MQ

Y si quieres ver el esquema que puede que para algunas conexiones sea más facil de ver como está conectado lo muestro tambien: Esquema del cableado de los sensores MQ

Calculos - Calibración

Cómo lo que recibimos de los sensores de gas son únicamente voltajes tenemos que mirar las fichas técnicas de cada sensor para poder determinar su sensibilidad a cada gas y de esta forma sacar su concentración en PPM.

Antes de nedada tenemos que calcular la resistencia inicial que tiene cada sensor. Usaremos los siguientes datos para calcularlo:

  • Las resistencia del PCB que mostraré para calcular R0 se obtiene mirando el sensor por la parte trasera y fijándote en el número de la resistencia central. En mi caso todos los sensores tienen la resistencia 102 que es una resistencia de 1 kΩ.
  • El voltaje de entrada es el voltaje con el que se alimenta el sensor de gas. Suele ser 5V aunque en mi caso midiendolo le llegan 4.9V.
  • El voltaje inicial es aquel que nos da el sensor en el pin de salida en un entorno concentración nula del gas que mide a 20ºC y 65% de humedad. Cada sensor dará un voltaje diferente. Es crucial dejar el sensor MQ encendido durante varios dias para quemar los residuos que pueda tener la resistencia y así obtener unos voltajes estables.
  • Resistencia al aire. En las tablas que pondré para calcular cada gas veremos que siembre hay una línea que en la leyenda pone "air" hay poner el valor de esta linea como resistencia al aire. Los números exactos de la resistencia al aire ya los he sacado con y se pueden ver en los calculos de cada sensor de gas por lo que no es necesario que midas la tabla para obtener el valor.

La fórmula para obtener la resistencia inicial de un sensor al aire es la siguiente:

R0 (Resistencia inicial) =
Resistencia PCB
Voltaje entrada - Voltaje inicial / Voltaje inicial
/ Resistencia aire

Lo segundo que vamos a calcular son las curvas de concentración de gases que tiene cada sensor. Como las fichas técnicas de las que tenemos que sacar los datos para las curvas de concentración y la resistencia al aire de cada sensor no nos lo pone de forma numérica he tenido que sacar estos datos de los gráficos logarítmicos de base 10 que mostraré a continuación mediante una plantilla de Excel y midiendo la distancia de los puntos entre su margen superior e inferior para obtener los datos.

Para obtener la trayectoria de cada gas necesitamos un punto inicial de la gráfica en logaritmo de base 10. En mi caso siempre he cogido el punto de menor concentración de cada gas en cada tabla como punto inicial. Ahora que tenemos un punto de la gráfica tenemos que saber la curvatura que tiene determinado gas en determinado sensor. De esta forma teniendo el eje X e Y más la curva del gas podemos conocer la concentración del gas en todas las situaciones con bastante veracidad.

curva =
log10(RS/R0 max) - log10(RS/R0 min) / log10(PPM max) - log10(PPM min)

Nota: La curva no es necesario que la calcules, ya la he calculado yo y siempre va ha ser la misma. Por lo que si no te intera calcula solo la resistencia inicial y saltas a la parte del código directamente que ya está puesto las curvaturas de cada sensor para cada gas.

MQ2

Cálculos de R0:

  • Resistencia al aire: 9,7 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0,114 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.114 / 0.114
/ 9.7
= 4.328
Grafica de gases MQ2

Cálculos para el punto de referencia y curvatura para los distintos gases:

GPL

  • Punto inicial: X = 200 PPM, Y = 1.7 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.26 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(1.7) = 0.230449

curva =
log10(0.26) - log10(1.7) / log10(10000) - log10(200)
= -0.479982

PROPANO

  • Punto inicial: X = 200 PPM, Y = 1.78 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.28 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(1.78) = 0.250420

curva =
log10(0.28) - log10(1.78) / log10(10000) - log10(200)
= -0.472793

HIDÓGENO

  • Punto inicial: X = 200 PPM, Y = 2.1 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.33 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(2.1) = 0.322219

curva =
log10(0.33) - log10(2.1) / log10(10000) - log10(200)
= -0.473054

ALCOHOL

  • Punto inicial: X = 200 PPM, Y = 2.89 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.65 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(2.89) = 0.460898

curva =
log10(0.65) - log10(2.89) / log10(10000) - log10(200)
= -0.381398

HUMO

  • Punto inicial: X = 200 PPM, Y = 3.43 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.61 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(3.43) = 0.535294

curva =
log10(0.61) - log10(3.43) / log10(10000) - log10(200)
= -0.441423

METANO

  • Punto inicial: X = 200 PPM, Y = 3.05 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.69 RS/R0

X referencia = log10(200) = 2,301030

Y referencia = log10(3.05) = 0.484300

curva =
log10(0.69) - log10(3.05) / log10(10000) - log10(200)
= -0.379909

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ2 tiene una R0 = 4.328 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
GPL 2.301030 0.230449 -0.479982
PROPANO 2.301030 0.250420 -0.472793
HIDRÓGENO 2.301030 0.322219 -0.473054
ALCOHOL 2.301030 0.460898 -0.381398
HUMO 2.301030 0.535294 -0.441422
METANO 2.301030 0.4842300 -0.379907

MQ3

Cálculos de R0:

  • Resistencia al aire: 60 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0,300 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.300 / 0.300
/ 60
= 0.255
Grafica de gases MQ3

Cálculos para el punto de referencia y curvatura para los distintos gases:

ALCOHOL

  • Punto inicial: X = 0.1 PPM, Y = 2.28 RS/R0
  • Punto final: X = 10 PPM, Y = 0.26 RS/R0

X referencia = log10(0.1) = -1

Y referencia = log10(2.28) = 0.357935

curva =
log10(0.26) - log10(2.28) / log10(10) - log10(0.1)
= -0.471481

BENCENO

  • Punto inicial: X = 0.1 PPM, Y = 4.49 RS/R0
  • Punto final: X = 10 PPM, Y = 0.8 RS/R0

X referencia = log10(0.1) = -1

Y referencia = log10(4.49) = 0.652246

curva =
log10(0.8) - log10(4.49) / log10(10) - log10(0.1)
= -0.374578

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ3 tiene una R0 = 0.255 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
ALCOHOL -1 0.357935 -0.471481
BENCENO 2.301030 0.652246 -0.374578

MQ4

Cálculos de R0:

  • Resistencia al aire: 4.45 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 1,150 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 1.150 / 1.150
/ 4.45
= 0.733
Grafica de gases MQ4

Cálculos para el punto de referencia y curvatura para los distintos gases:

METANO

  • Punto inicial: X = 200 PPM, Y = 1.8 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.44 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(1.8) = 0.255273

curva =
log10(0.44) - log10(1.8) / log10(10000) - log10(200)
= -0.360112

GPL

  • Punto inicial: X = 200 PPM, Y = 2.6 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.73 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(2.6) = 0.414973

curva =
log10(0.73) - log10(2.6) / log10(10000) - log10(200)
= -0.324697

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ4 tiene una R0 = 0.733 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
METANO 2.301030 0.255273 -0.360112
GPL 2.301030 0.414973 -0.324697

MQ5

Cálculos de R0:

  • Resistencia al aire: 6.5 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0.848 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.848 / 0.848
/ 6.5
= 0.735
Grafica de gases MQ5

Cálculos para el punto de referencia y curvatura para los distintos gases:

GPL

  • Punto inicial: X = 200 PPM, Y = 0.7 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.155 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(0.7) = -0.154902

curva =
log10(0.155) - log10(0.7) / log10(10000) - log10(200)
= -0.385390

METANO

  • Punto inicial: X = 200 PPM, Y = 0.94 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.207 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(0.94) = -0.026872

curva =
log10(0.207) - log10(0.94) / log10(10000) - log10(200)
= -0.386798

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ5 tiene una R0 = 0.735 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
GPL 2.301030 -0.154902 -0.358375
METANO 2.301030 -0.026872 -0.386798

MQ7

Cálculos de R0:

  • Resistencia al aire: 26.75 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0.626 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.626 / 0.626
/ 26.75
= 0.255
Grafica de gases MQ7

Cálculos para el punto de referencia y curvatura para los distintos gases:

HIDRÓGENO

  • Punto inicial: X = 50 PPM, Y = 1.36 RS/R0
  • Punto final: X = 4000 PPM, Y = 0.09 RS/R0

X referencia = log10(50) = 1.698970

Y referencia = log10(1.36) = 0.133539

curva =
log10(0.09) - log10(0.7) / log10(4000) - log10(50)
= -0.619674

CO (Monóxido de carbono)

  • Punto inicial: X = 50 PPM, Y = 1.66 RS/R0
  • Punto final: X = 4000 PPM, Y = 0.052 RS/R0

X referencia = log10(50) = 1.698970

Y referencia = log10(1.36) = -0.220108

curva =
log10(0.052) - log10(1.36) / log10(4000) - log10(50)
= -0.790349

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ7 tiene una R0 = 0.255 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
HIDRÓGENO 1.698970 0.133539 -0.619674
CO 1.698970 0.220108 -0.790349

MQ8

Cálculos de R0:

  • Resistencia al aire: 70 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 1.072 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 1.072 / 1.072
/ 70
= 0.051
Grafica de gases MQ8

Cálculos para el punto de referencia y curvatura para los distintos gases:

HIDRÓGENO

  • Punto inicial: X = 200 PPM, Y = 8.56 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.029 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(8.56) = 0.932474

curva =
log10(0.029) - log10(8.56) / log10(10000) - log10(200)
= -1.453867

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ8 tiene una R0 = 0.051 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
HIDRÓGENO 2.301030 0.932474 -1.453867

MQ9

Cálculos de R0:

  • Resistencia al aire: 9.9 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0.474 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.474 / 0.474
/ 9.9
= 0.943
Grafica de gases MQ9

Cálculos para el punto de referencia y curvatura para los distintos gases:

CO (Monóxido de carbono)

  • Punto inicial: X = 200 PPM, Y = 1.69 RS/R0
  • Punto final: X = 1000 PPM, Y = 0.782 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(1.69) = 0.227886

curva =
log10(0.782) - log10(1.69) / log10(1000) - log10(200)
= -0.478819

GPL

  • Punto inicial: X = 200 PPM, Y = 2.11 RS/R0
  • Punto final: X = 10000 PPM, Y = 0.333 RS/R0

X referencia = log10(200) = 2.301030

Y referencia = log10(2.11) = 0.324282

curva =
log10(0.333) - log10(2.11) / log10(10000) - log10(200)
= -0.471955

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ9 tiene una R0 = 0.943 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
CO 2.301030 0.227886 -0.478819
GPL 2.301030 0.324282 -0.471955

MQ135

Cálculos de R0:

  • Resistencia al aire: 3.65 RS/R0
  • Voltaje entrada: 4,9 V
  • Voltaje inicial: 0.748 V
  • Resistencia PCB: 1 kΩ
R0 =
1
4.9 - 0.748 / 0.748
/ 3.65
= 1.521
Grafica de gases MQ135

Cálculos para el punto de referencia y curvatura para los distintos gases:

ACETONA

  • Punto inicial: X = 10 PPM, Y = 1.51 RS/R0
  • Punto final: X = 200 PPM, Y = 0.579 RS/R0

X referencia = log10(10) = 1

Y referencia = log10(1.51) = 0.319976

curva =
log10(0.579) - log10(1.51) / log10(200) - log10(10)
= -0.319976

TOLUENO

  • Punto inicial: X = 10 PPM, Y = 1.62 RS/R0
  • Punto final: X = 200 PPM, Y = 0.635 RS/R0

X referencia = log10(10) = 1

Y referencia = log10(1.62) = 0.209515

curva =
log10(0.635) - log10(1.62) / log10(200) - log10(10)
= -0.312630

TOLUENO

  • Punto inicial: X = 10 PPM, Y = 1.92 RS/R0
  • Punto final: X = 200 PPM, Y = 0.733 RS/R0

X referencia = log10(10) = 1

Y referencia = log10(1.92) = 0.293301

curva =
log10(0.733) - log10(1.92) / log10(200) - log10(10)
= -0.321435

Los datos de calibración a ingresar en el futuro programa es que el sensor MQ9 tiene una R0 = 0.943 y la tabla con el punto de referencia de cada gas y su respectiva curva.

Eje X Eje Y Curva
ACETONA 1 0.178977 -0.319976
TOLUENO 1 0.209515 -0.312630
ALCOHOL 1 0.283301 -0.321435

Calculos - PPM

Con los cálculos previos hechos e ingresados en el programa ahora explicaré que cálculos se hacen en el interior del programa para determinar la concentración de un gas.

  • Cálculo de resistencia:
    RS =
    R pcb * (V entrada - V inicial) / V inicial
  • Cáculo ratio:
    Ratio =
    Rs / R0
  • Cáculo concentración:
    Gas ppm = 10^(
    ln Ration - GasEjeY / GasCurva
    ) + GasEjeX

Como es complicado verlo sin un ejemplo voy hacer un cálculo práctico cogiendo los datos del gas hidrógeno en el sensor MQ2.

Los parámetros a introducir son la resistencia de la PCB del sensor que en MQ2 es de 1K Ohms, voltaje de entrada que son 4,9 V y el voltaje de la salida analógica que imáginemos que son 0.150 V.

RS =
1 * (4.9 - 0.150) / 0.150
= 31,67

Para calcular el ratio necesitamos dividir el resultado obtenido antes por la R0 de sensor MQ2 que como hemos calculado previamente es 4.328

Ratio =
31,67 / 4.328
= 7.317

Para saber la concentración de determinado gas en PPM tenemos que calcular el exponente de la base 10. Este exponente tiene varios cálculos en su interior.

  1. El primer calculo que tenemos que hacer es la resta del logaritmo natural del ratio conseguido anteriormente menos la constante del eje Y para el gas a calcular, en este caso el hidrógeno del MQ2 con el valor calculado 0.322. La resta se ve así:

    ln(7.317) - 0,322 = 1.668

  2. En segundo lugar, hay que dividir el resultado obtenido entre la curvatura calculada del gas, siendo esta curvatura para el hidrógeno en el sensor MQ2 de -0,473 quedando el cálculo de la siguiente forma:
    1.668 / -0.473
    = -3,526
  3. El tercer paso y con el que terminaremos de calcular el exponente es sumarle el valor de la constante del X que es 2,301:

    -3.526 - 2.301 = -1,225

  4. Por último, teniendo el exponente ya calculado que es -0,923 simplemente tenemos que calcular 10^(-1,225)=0,059 PPM de hidrógeno leído por el sensor MQ2.

Funcionamiento

Cada sensor MQ tiene su propia clase para almacenar sus calibraciones de gases, R0, inicio de hilo de lectura propio, variables de resultados de gases, alarma y ruta I2C y GPIO usadas para la trasmisión de datos digitales y analógicos.

Como la medición es continua también dispone de un contador de ciclos de lectura y el sumatorio de voltajes leídos. El intervalo de lectura es de 5 segundos.

Como todos los sensores de gases tienen que hacer los mismos cálculos estos están en una clase a parte para evitar redundancia. Los cálculos se hacen por cada gas en cada sensor.

En el caso de que el ADC falle y no se puedan leer los gases la clase eliminará su propio hilo de ejecución para dejar de generar errores de lectura y marcará como False la variable funcionamiento para que el programa principal sepa que ese sensor no funciona. El programa principal volverá a intentar leer el sensor en el siguiente ciclo.

Diagrama del sensor sht31

Código

Como se ha comentado anteriormente hay una clase por cada sensor. A parte de estas clases tenemos una clase con los métodos que usan todos los gases para calcular las PPM y otra clase que estará en la carpeta test que vamos a mostar primero.

Test

Es un script lo más sencillo posible para validar que el sensor funciona y ver su voltaje para poder calibrar los sensores. En la variable ADDRESS y PIN tendremos que poner los valores que vimos en la tabla del cableado de los sensores.

# nativas de Python
import time

# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button


# direccion I2C del ADS11115
ADDRESS = 0x4a  # address conectado a VDD
PIN = 3  # canal analógico del chip

convertor = ads1115.ADS1115

i2c = busio.I2C(board.SCL, board.SDA)
adc = convertor(i2c=i2c, address=ADDRESS)

channel = AnalogIn(adc, PIN)

while True:
    print(channel.voltage)
    time.sleep(1)

Calculos PPM

Esta es la clase general que usan todos los sensores MQ para determinar su concentración de gas. Aquí llevamos a la práctica los calculos hechos en la sección de calculos.

# calculos_gas.py
import math
import logging


class Calculos:
    '''Tiene los métodos necesarios para calculas las
    PPM de cualquier sensor MQ'''

    @classmethod
    def calcular_voltaje(cls, voltaje_acumulado, cantidad_muestras):
        ''' Calcula el voltaje de media leido en un intervalo de tiempo'''

        voltaje = 0
        if (cantidad_muestras != 0):
            voltaje = voltaje_acumulado / cantidad_muestras

            # en el caso de que el sensor no funcione te ponemos un valor
            # artificial en el voltaje para que el programa pueda seguir
            # funcionando.
            if(voltaje <= 0):
                voltaje = 0.001
                logging.error('Sensor de gas inutilizado')

        return voltaje

    @classmethod
    def calcular_resistencia(cls, voltaje, resistencia):
        '''Con el voltaje y la resistencia del PCB del sensor podemos calcular
        la resistentencia que te tendrá el sensor. Esta resistencia se
        representa como Rs.'''

        resultado = 0

        if (voltaje != 0):
            resultado = float(
                resistencia * (4.9 - float(voltaje)) / float(voltaje))
        return resultado

    @classmethod
    def calcular_ppm(cls, ratio, curva_gas):
        '''Mediante calculos logaritmicos nos devuelve los ppm de un gas
        en particular.'''

        concentracion = math.pow(
            10,
            ((math.log(ratio) - curva_gas[1]) / curva_gas[2]) + curva_gas[0]
        )
        return str(round(concentracion, 3))

Clases MQ

Cada sensor tiene su propia clase ya que cada uno tiene sus propios datos.

MQ2

# sensor_mq2.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x49  # pin address conectado a VDD
PIN = 0  # canal analógico del chip

alarma_mq2 = 0


class MQ2:
    """Detector de gas MQ2"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(14)

    # Ro value of the sensor
    ro = 4.328

    # Curva logaritmica con base 10
    HIDROGENO_CURVA = [2.301030, 0.322219, -0.473054]
    METANO_CURVA = [2.301030, 0.484300, -0.379901]
    GPL_CURVA = [2.301030, 0.230449, -0.479982]
    PROPANO_CURVA = [2.301030, 0.250420, -0.472794]
    ALCOHOL_CURVA = [2.301030, 0.460898, -0.381398]
    HUMO_CURVA = [2.301030, 0.535294, -0.441423]

    # gas en ppm
    hidrogeno_ppm = 0
    metano_ppm = 0
    gpl_ppm = 0
    propano_ppm = 0
    alcohol_ppm = 0
    humo_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ2'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)
            self.channel = AnalogIn(adc, pin)

            self.sched_mq2 = BackgroundScheduler()
            self.sched_mq2.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq2")
            self.sched_mq2.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ2 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ2 ha dejado de funcionar.')

            self.sched_mq2.remove_job("sched_mq2")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.hidrogeno_ppm = calculos.calcular_ppm(ratio, self.HIDROGENO_CURVA)

        self.metano_ppm = calculos.calcular_ppm(ratio, self.METANO_CURVA)

        self.gpl_ppm = calculos.calcular_ppm(ratio, self.GPL_CURVA)

        self.propano_ppm = calculos.calcular_ppm(ratio, self.PROPANO_CURVA)

        self.alcohol_ppm = calculos.calcular_ppm(ratio, self.ALCOHOL_CURVA)

        self.humo_ppm = calculos.calcular_ppm(ratio, self.HUMO_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq2

        alarma_mq2 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq2

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq2 = 0

    def get_hidrogeno(self):
        '''Devuelve un string de la variable hidrogeno_ppm con tres decimales'''

        return self.hidrogeno_ppm

    def get_metano(self):
        '''Devuelve un string de la variable metano_ppm con tres decimales'''

        return self.metano_ppm

    def get_gpl(self):
        '''Devuelve un string de la variable gpl_ppm con tres decimales'''

        return self.gpl_ppm

    def get_propano(self):
        '''Devuelve un string de la variable propano_ppm con tres decimales'''

        return self.propano_ppm

    def get_alcohol(self):
        '''Devuelve un string de la variable alcohol_ppm con tres decimales'''

        return self.alcohol_ppm

    def get_humo(self):
        '''Devuelve un string de la variable humo_ppm con tres decimales'''

        return self.humo_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq2

        return str(alarma_mq2)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ3

# sensor_mq3.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x49  # pin address conectado a VDD
PIN = 1  # canal analógico del chip

alarma_mq3 = 0


class MQ3:
    """Detector de gas MQ3"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(15)

    # Ro value of the sensor
    ro = 0.255

    # Curva logaritmica con base 10
    ALCOHOL_CURVA = [-1, 0.357935, -0.471481]
    BENCENO_CURVA = [-1, 0.652246, -0.374582]

    # gas en ppm
    alcohol_ppm = 0
    benceno_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ3'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:

            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)
            self.channel = AnalogIn(adc, pin)

            self.sched_mq3 = BackgroundScheduler()
            self.sched_mq3.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq3")
            self.sched_mq3.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ3 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ3 ha dejado de funcionar.')

            self.sched_mq3.remove_job("sched_mq3")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.alcohol_ppm = calculos.calcular_ppm(ratio, self.ALCOHOL_CURVA)

        self.benceno_ppm = calculos.calcular_ppm(ratio, self.BENCENO_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq3

        alarma_mq3 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq3

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq3 = 0

    def get_alcohol(self):
        '''Devuelve un string de la variable alcohol_ppm con tres decimales'''

        return self.alcohol_ppm

    def get_benceno(self):
        '''Devuelve un string de la variable benceno_ppm con tres decimales'''

        return self.benceno_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq3

        return str(alarma_mq3)

    def get_voltaje(self):
        '''Devuelve un String de la variable alarma'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ4

# sensor_mq4.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x49  # pin address conectado a VDD
PIN = 2  # canal analógico del chip

alarma_mq4 = 0


class MQ4:
    """Detector de gas MQ4"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(23)

    # Constante de resistencia inicial
    ro = 0.733

    # Curva logaritmica con base 10
    METANO_CURVA = [2.301030, 0.255273, -0.385390]
    GPL_CURVA = [2.301030, 0.414973, -0.386798]

    # gas en ppm
    metano_ppm = 0
    gpl_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ4'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq4 = BackgroundScheduler()
            self.sched_mq4.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq4")
            self.sched_mq4.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ4 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

            print(str(self.voltaje_acumulado) +
                  " || " + str(self.cantidad_muestras))

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ4 ha dejado de funcionar.')

            self.sched_mq4.remove_job("sched_mq4")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.metano_ppm = calculos.calcular_ppm(ratio, self.METANO_CURVA)

        self.gpl_ppm = calculos.calcular_ppm(ratio, self.GPL_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq4

        alarma_mq4 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq4

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq4 = 0

    def get_metano(self):
        '''Devuelve un string de la variable metano_ppm con tres decimales'''

        return self.metano_ppm

    def get_gpl(self):
        '''Devuelve un string de la variable gpl_ppm con tres decimales'''

        return self.gpl_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq4

        return str(alarma_mq4)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ5

# sensor_mq5.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging

# direccion I2C del ADS11115
ADDRESS = 0x49  # pin address conectado a VDD
PIN = 3  # canal analógico del chip

alarma_mq5 = 0


class MQ5:
    """Detector de gas MQ5"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(24)

    # Ro value of the sensor
    ro = 0.735

    # Curva logaritmica con base 10
    GPL_CURVA = [2.301030, -0.154902, -0.385390]
    METANO_CURVA = [2.301030, 0.026872, -0.386798]

    # gas en ppm
    gpl_ppm = 0
    metano_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ5'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq5 = BackgroundScheduler()
            self.sched_mq5.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq5")
            self.sched_mq5.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ5 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ5 ha dejado de funcionar.')

            self.sched_mq5.remove_job("sched_mq5")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.gpl_ppm = calculos.calcular_ppm(ratio, self.GPL_CURVA)

        self.metano_ppm = calculos.calcular_ppm(ratio, self.METANO_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq5

        alarma_mq5 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq5

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq5 = 0

    def get_gpl(self):
        '''Devuelve un string de la variable gpl_ppm con tres decimales'''

        return self.gpl_ppm

    def get_metano(self):
        '''Devuelve un string de la variable metano_ppm con tres decimales'''

        return self.metano_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq5

        return str(alarma_mq5)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ7

# sensor_mq7.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x4a  # pin address conectado a VDD
PIN = 0  # canal analógico del chip

alarma_mq7 = 0


class MQ7:
    """Detector de gas MQ7"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(25)

    # Ro value of the sensor
    ro = 0.255

    # Curva logaritmica con base 10
    HIDROGENO_CURVA = [1.698970, 0.133539, -0.619674]
    CO_CURVA = [1.698970, 0.220108, -0.790349]

    # gas en ppm
    hidrogeno_ppm = 0
    co_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ7'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq7 = BackgroundScheduler()
            self.sched_mq7.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq7")
            self.sched_mq7.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ7 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ7 ha dejado de funcionar.')

            self.sched_mq7.remove_job("sched_mq7")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.hidrogeno_ppm = calculos.calcular_ppm(ratio, self.HIDROGENO_CURVA)

        self.co_ppm = calculos.calcular_ppm(ratio, self.CO_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq7
        alarma_mq7 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq7

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq7 = 0

    def get_hidrogeno(self):
        '''Devuelve un string de la variable hidrogeno_ppm con tres decimales'''

        return self.hidrogeno_ppm

    def get_co(self):
        '''Devuelve un string de la variable co_ppm con tres decimales'''

        return self.co_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq7

        return str(alarma_mq7)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ8

# sensor_mq8.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging

# direccion I2C del ADS11115
ADDRESS = 0x4a  # pin address conectado a VDD
PIN = 1  # canal analógico del chip

alarma_mq8 = 0


class MQ8:
    """Detector de gas MQ8"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(17)

    # Ro value of the sensor
    ro = 0.051

    # Curva logaritmica con base 10
    HIDROGENO_CURVA = [2.301030, 0.932474, -1.453867]

    # gas en ppm
    hidrogeno_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ8'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq8 = BackgroundScheduler()
            self.sched_mq8.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq8")
            self.sched_mq8.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ8 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ8 ha dejado de funcionar.')

            self.sched_mq8.remove_job("sched_mq8")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.hidrogeno_ppm = calculos.calcular_ppm(ratio, self.HIDROGENO_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq8

        alarma_mq8 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq8

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq8 = 0

    def get_hidrogeno(self):
        '''Devuelve un string de la variable hidrogeno_ppm con tres decimales'''

        return self.hidrogeno_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq8

        return str(alarma_mq8)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ9

# sensor_mq9.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x4a  # pin address conectado a VDD
PIN = 2  # canal analógico del chip

alarma_mq9 = 0


class MQ9:
    """Detector de gas MQ9"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(27)

    # Ro value of the sensor
    ro = 0.943

    # Curva logaritmica con base 10
    CO_CURVA = [2.301030, 0.227887, -0.478819]
    GPL_CURVA = [2.301030, 0.324282, -0.471955]

    # gas en ppm
    co_ppm = 0
    gpl_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ9'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq9 = BackgroundScheduler()
            self.sched_mq9.add_job(self.leer_voltaje, 'interval',
                                   seconds=5, id="sched_mq9")
            self.sched_mq9.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ9 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ9 ha dejado de funcionar.')

            self.sched_mq9.remove_job("sched_mq9")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.co_ppm = calculos.calcular_ppm(ratio, self.CO_CURVA)

        self.gpl_ppm = calculos.calcular_ppm(ratio, self.GPL_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq9

        alarma_mq9 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq9

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq9 = 0

    def get_co(self):
        '''Devuelve un string de la variable co_ppm con tres decimales'''

        return self.co_ppm

    def get_gpl(self):
        '''Devuelve un string de la variable gpl_ppm con tres decimales'''

        return self.gpl_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq9

        return str(alarma_mq9)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma

MQ135

# sensor_mq135.py
# I2C comunicación
import board
import busio

# librerías del ADC ADS1115
import adafruit_ads1x15.ads1115 as ads1115
from adafruit_ads1x15.analog_in import AnalogIn

# lectura digital
from gpiozero import Button

# Medición por intervalos
from apscheduler.schedulers.background import BackgroundScheduler

# Calcular PPM
from calculos_gas import Calculos

# para registro de errores
import logging


# direccion I2C del ADS11115
ADDRESS = 0x4a  # pin address conectado a VDD
PIN = 3  # canal analógico del chip

alarma_mq135 = 0


class MQ135:
    """Detector de gas MQ135"""

    # resistencia del sensor
    LOAD_RESISTANCE = 1

    # pin salida digital
    DOutput = Button(22)

    # Ro value of the sensor
    ro = 1.521

    # Curva logaritmica con base 10
    ACETONA_CURVA = [1, 0.178977, -0.319976]
    TOLUENO_CURVA = [1, 0.209515, -0.312630]
    ALCOHOL_CURVA = [1, 0.283301, -0.321436]

    # gas en ppm
    acetona_ppm = 0
    tolueno_ppm = 0
    alcohol_ppm = 0
    voltaje = 0
    funcionamiento = True

    # Contadores
    voltaje_acumulado = 0
    cantidad_muestras = 0

    def __init__(self, convertor=ads1115.ADS1115, pin=PIN, address=ADDRESS):
        '''Inicia con la lectura periodica de gases del sensor MQ135'''

        # Permique que el resto del programa sigua funcionando aunque el ADC no funcione
        try:
            i2c = busio.I2C(board.SCL, board.SDA)
            adc = convertor(i2c=i2c, address=address)

            self.channel = AnalogIn(adc, pin)

            self.sched_mq135 = BackgroundScheduler()
            self.sched_mq135.add_job(self.leer_voltaje, 'interval',
                                     seconds=5, id="sched_mq135")
            self.sched_mq135.start()

        except (ValueError, OSError):
            self.funcionamiento = False

            logging.error('El ADC del MQ135 ha dejado de funcionar.')

    def leer_voltaje(self):
        '''leerá el voltaje cada cierto tiempo y lo irá acumulando, además
        lleva la cuenta de cuantos ciclos de lectura lleva.'''

        # Evita que se rompa el programa en caso de que el ADC deje de funcionar a mitad
        # de una lectura de voltaje.
        try:
            self.voltaje_acumulado += self.channel.voltage
            self.cantidad_muestras += 1

        except (ValueError, OSError):
            self.funcionamiento = False
            logging.error('El ADC del MQ135 ha dejado de funcionar.')

            self.sched_mq135.remove_job("sched_mq135")

    def finalizar_ciclo(self):
        '''Llama a las funciones necesarias para finalizar el ciclo de lectura
        de datos y empezar uno nuevo'''

        self.calcular_concentracion()

        self.reiniciar_contadores()

    def calcular_concentracion(self):
        '''Hace los calculos necesarios para obtener las concentraciones
        de los gases en ppm'''

        calculos = Calculos()

        self.voltaje = calculos.calcular_voltaje(
            self.voltaje_acumulado, self.cantidad_muestras)

        rs = calculos.calcular_resistencia(self.voltaje, self.LOAD_RESISTANCE)

        ratio = rs / self.ro

        self.acetona_ppm = calculos.calcular_ppm(ratio, self.ACETONA_CURVA)

        self.tolueno_ppm = calculos.calcular_ppm(ratio, self.TOLUENO_CURVA)

        self.alcohol_ppm = calculos.calcular_ppm(ratio, self.ALCOHOL_CURVA)

    def activar_alarma(self):
        '''Pasa la variable alarma a True en el caso de que el pin digital
        envien una señal.'''

        global alarma_mq135

        alarma_mq135 = 1

    def reiniciar_contadores(self):
        '''Reinicia los valores de los contadores para tomar nuevas lecturas.'''

        global alarma_mq135

        self.voltaje_acumulado = 0
        self.cantidad_muestras = 0
        alarma_mq135 = 0

    def get_acetona(self):
        '''Devuelve un string de la variable acetona_ppm con tres decimales'''

        return self.acetona_ppm

    def get_tolueno(self):
        '''Devuelve un string de la variable tolueno_ppm con tres decimales'''

        return self.tolueno_ppm

    def get_alcohol(self):
        '''Devuelve un string de la variable alcohol_ppm con tres decimales'''

        return self.alcohol_ppm

    def get_alarma(self):
        '''Devuelve un String de la variable alarma'''

        global alarma_mq135

        return str(alarma_mq135)

    def get_voltaje(self):
        '''Devuelve un String de la variable voltaje'''

        return str(self.voltaje)

    def get_funcionamiento(self):
        '''Nos dice mediante un boolean si el sensor funciona. En el caso de True
        es que el sensor funciona'''

        return self.funcionamiento

    # cuando el sensor detecte una concentración peligrosa lanzará una alarma
    DOutput.when_pressed = activar_alarma