Leyendo el SDS011 con Python

Objetivos

 

  • Siguiendo con el SDS011 veremos leerlo desde un PC con Python.
  • Sensor SDS011 y varios programas con diferente sobjetivos
  • Leyendo el SDS011 desde un PC.[/three-fourth] [clear/]
  •  

    Material requerido.

    Sensor de particulas en suspension  Sensor SDS011
    personal computer  Un PC

     

    Leyendo el SDS011 desde un PC

     

    En la última sesión aprovechamos para presentar el sensor de partículas en suspensión SDS011 y vimos cómo usarlo con Arduino sin gran dificultad, pero aprovechando que el Pisuerga pasa por Valladolid, quedé en probar el interface USB que viene de serie con el sensor, para leerlo desde el PC y ver si es viable lanzar un data logger directamente desde nuestros PC

    Tras un breve análisis queda claro que usar Python para leer el dispositivo USB, es vergonzosamente fácil. De hecho, el propio fabricante, nos ofrece un programa para hacer esto, así que podemos ir al lio cuando queramos… pero antes, vale la pena hacer un alegato

    No sé porque razón no se usa más Python en los centros educativos. No seré yo quien intente dar lecciones, pero siempre me sorprende que lo poco que se emplea y no lo entiendo, porque me parece ideal.

    Es sencillo y sensato de aprender, no tiene puntos y comas ni manías raras, no hay que definir tipos, es versátil y potente hasta lo increíble y en cierto modo es heredero de aquellos míticos Basic con los que todos los viejillos aprendimos a programar porque tiene la virtud de ser interpretado (No hay que compilar)

    Y por si fuera poco tiene una enorme potencia gracias a lo modular de su concepción y a su capacidad de importar librerías de lo que se os ocurra. Python es un lenguaje ideal para mezclar churras con merinas y más en el caso de sensores de distintos fabricantes y lo que se os pueda ocurrir

    Así que voy a usar este tuto para jugar con las posibilidades que nos brinda Python y la facilidad con la que podemos integrar aplicaciones y prestaciones que serían impensables de otro modo

     

    Leyendo el Nova SDS011 con Python

     

    Podemos encontrar el programa que nos ofrece el fabricante Nova,  aquí,  y podemos pasar a describir brevemente el programa para los que sean nuevos en Python.

    Para empezar Python nos permite importar módulos en función de las necesidades. Por ejemplo, para usar la puerta serie vamos a importar un modulo de puertas serie (Asombrosamente) llamado Py serial. Si no lo tenemos instalado previamente, podemos descargarlo con este comando desde una consola de comandos:

     pip install pyserial

    Esto descargará todo lo necesario para dejar disponible la librería para nuestros programas

    • Si has instalado Python 3 correctamente, deberías tener el pip instalado también, pero en caso contrario haz una búsqueda rápida en Google para instalarlo o lee esto .

    Una vez instalados los módulos que vamos a usar podemos empezar con el programa

    from __future__ import print_function
    from serial import Serial, EIGHTBITS, STOPBITS_ONE, PARITY_NONE
    import time, struct

    Básicamente, estas líneas importan parte de las librerías (No todo, por ahorrar memoria) que vamos a usar. Aunque hemos descargado con pip el pyserial, las otras vienen de serie con Python 3. Vamos a definir los parámetros de la conexión serie:

    port = "com6"
    baudrate = 9600

    En mi caso, el sensor SDS011 se ha conectado al COM6 de mi Windows, pero en tu caso deberás comprobar en que puerta está y cambiarlo aquí. Lo mismo que si estas en Linux o Mac, el nombre será distinto pero la idea es la misma. Un acosa encantadora de Python, es que el programa correrá sin modificación en las tres plataformas. ¿Qué te parece?

    Tenemos ahora algunos comandos para el sensor, que solo se los sabe el fabricante (O los que se leen los manuales, esa extraña tribu)

    HEADER_BYTE = b"\xAA"
    COMMANDER_BYTE = b"\xC0"
    TAIL_BYTE = b"\xAB"
    
    byte, previousbyte = b"\x00", b"\x00"

    Usaremos estos comandos, para ponernos de acuerdo con el sensor, pero en realidad, afortunadamente, el fabricante si que se ha leído el manual y nos pone a huevo el manejo del sensor. Y ya podemos ir al programa principal:

    while True:
       previousbyte = byte
       byte = ser.read(size=1)
       #print(byte)

    La primera línea es para crear un bucle infinito, y dentro viene la parte de hablar con el sensor, de lo que no merece la pena que hablemos mucho:

    if previousbyte == HEADER_BYTE and byte == COMMANDER_BYTE:
       packet = ser.read(size=8) # Read 8 more bytes
       # Decode the packet,little endian,2 shorts for pm2.5 and pm10, 2 ID bytes, checksum.
       readings = struct.unpack('<HHcccc', packet)

    Estas líneas comprueban que el mensaje que nos envía el SDS011 tiene una estructura valida y en caso afirmativo lee el mensaje a una estructura definida para ello de la que sacaremos fácilmente la información de las lecturas, a un par de variables:

    pm_25 = readings[0]/10.0
    pm_10 = readings[1]/10.0

    Ahora vienen unas líneas que calculan un checksum para asegurarnos de que no ha habido errores en la transmisión (Que podéis ignorar olímpicamente)

    checksum = readings[4][0]
    calculated_checksum = sum(packet[:6]) & 0xFF
    checksum_verified = (calculated_checksum == checksum)

    Si nunca habéis trabajado con Python o con este tipo de sensores, lo de arriba os parecerá incomprensible, pero en realidad es una tontería. El fabricante define un formato de mensaje con el que envía los datos y un procedimiento para leerlo con una estructura. Si comprobamos que el mensaje es valido (Porque tiene la estructura adecuada) leemos los datos y los dejamos en un par de variables. Es mas sencillo de lo que parece una vez que entiendes el procedimiento, pero además esto no es importante, porque ya tienes un programa que lo lee.

    Y ahora ya podemos imprimir las lecturas a la consola Python:

    tail = readings[5]
    if tail == TAIL_BYTE and checksum_verified:
       print("PM 2.5:", pm_25, "μg/m^3  PM 10:", pm_10, "μg/m^3")
       print("PM 2.5:", pm_25, "µg/m³  PM 10:", pm_10, "µg/m³  ID:", bytes(id).hex())

    SI ejecutas el programa Python, (Con el sensor SDS011 conectado al USB) obtendrás directamente las lecturas del sensor enviadas a través del puerto USB:

    Muesta de lecturas del sds011

    Si quitas la parte, más técnica, de comprobar la validez de la trama y los checksums, no es para tanto. Lo importante aquí, es que resulta de lo mas sencillo recoger los datos del puerto serie a nuestra consola ¿No te parece?

     

    Complicando un poco más el programa

     

    Ha sido tan fácil que no me van a pagar esto si no lo complico un poco. Podríamos añadirle alguna funcionalidad más, por ejemplo, que grabe estos datos a un fichero en disco para guardar registro de las lecturas o Data Log.

    Para ello no necesitamos ningún import adicional, y podemos empezar definiendo la hora de las lecturas, que grabaremos al principio de cada línea que vaya al fichero de registro:

    pm_25 = readings[0]/10.0
    pm_10 = readings[1]/10.0
    T = time.strftime("%Y/%B/%d %H:%M:%S")
    count += 1

    Utilizamos una función de time para convertirla en texto con formato comprensible. No es cuestión de liarnos a hablar de los formatos posibles, pero básicamente, el formato entre comillas, significa que ponga

    Año/Mes/Dia horas:minutos:segundos

    Y ahora, ya podemos crear un String que lleve toda la información:

    line = str(count) + "\t" + T + "\t" + str(pm_25) + "\t" + str(pm_10) +"\n"

    Donde count es solou na variable que va aumentando para calcular el número de línea leído (Perfectamente prescindible, pero que nos permitirá comprobar que no perdemos líneas) y los “\t” indican un tabulador en la string, más un fin de línea final “\n”

    Para añadir la línea al fichero bastaría con hacer write(line) pero hay antes que haber abierto el fichero previamente y (Importante) cerrarlo para liberarlo, porque como vamos a estar grabando repetidamente hay que cerrar el fichero en algún momento para asegurar que los datos quedan grabados y por eso vamos a complicar un poco más el programa

    Entrando en detalles tenemos que abrir el fichero, que nos devolverá un identificador (Porque podríamos tener abiertos múltiples ficheros), grabar datos y cerrar:

    log = open("d:\Log.txt", "a")

    Donde pasamos el nombre del fichero que usaremos como registro con su ruta completa y el tipo de apertura que nos interesa, en mi caso “a”, que significa append, es decir crear el fichero si no existe, pero añadir las líneas al final en caso de que exista, respetando las líneas previas

    line = str(count) + "\t" + T + "\t" + str(pm_25) + "\t" + str(pm_10) +"\n"
    log.write( line)
    log.close()

    Pero hacer esto cada vez es feo (Poco elegante) por lo que podemos hacer algo mas presentable como escribir solo cada tantas líneas (10 por ejemplo), y entonces abrir, escribir la tanda de lecturas, y cerrar el fichero:

    count += 1
    if ((count % 10) == 0):   # Cada cuantas lineas se graba el lote
       log.close()
       log = open("d:\Log.txt", "a")
    
    line = str(count) + "\t" + T + "\t" + str(pm_25) + "\t" + str(pm_10) +"\n"
    log.write( line)

    Cada 10 (O el número de líneas que prefieras) líneas, el resto entero de count será 0, por lo que se cumplirá la condición y cerraremos el fichero de log forzando la grabación de los datos, para después abrirlo de nuevo y grabar los datos. El resultado sería:

    Registro Python de lecturas de particulas

    Aquí os dejo la segunda versión del programa completo probado y funcionando:

    SDS011 2 log

    No esta mal ¿No? Hemos leído el sensor y registrado sus datos a un fichero en menos de lo que pensabas. La ventaja de usar Python es que es muy fácil hacer las cosas tirando de las librerías y como su sintaxis es sencilla eliminamos muchos errores.

     

    ¿Podríamos Hacer un grafico de los valores que leemos?

     

    Puestos a pedir, ¿Porque no? ¿Sería complicado? Pues vamos a ver, como os comentaba, en Python hay librerías disponibles para lo que se te pueda ocurrir. De hecho, el problema es que en el tema de gráficos hay varias para elegir y tras una breve inspección he optado por la pyplot de mathplotlib

    No soy un experto (Ni siquiera un iniciado), pero la mathplotlib es una librería de lo mas extensa y no pienso ponerme a hablar de ella por pura ignorancia, pero resulta fácil de importar y no necesitamos traer todo. Solo la parte que nos interesa:

    import matplotlib.pyplot as plt

    Esta librería es capaz de generar los gráficos a partir de listas que contengas los valores a mostrar y otra lista con los datos a marcar en abscisas, lo que no pone muy a tiro los gráficos. Empecemos creando las listas que van a contener las lecturas:

    datax = [ ]
    PM25 = [ ]
    PM10 = []

    La cuestión ahora es ir rellenando estas listas con cada una de las nuevas lecturas a medida que se produzcan dentro del bucle principal:

    datax += [time.strftime("%H:%M:%S")]
    PM25  += [ pm_25]
    PM10  +=[pm_10]

    Cada una de estas líneas hacen la lista correspondiente, reciba la nueva lectura, añadiéndosela al final, de modo que va aumentando a medida que va produciéndose y ahora solo nos falta lanzar el grafico:

    plt.plot(datax, PM25)
    plt.plot(datax, PM10)
    plt.draw()
    plt.pause(1e-17)
    time.sleep(0.1)

    Iremos obteniendo una gráfica como esta:

    Grafico con mathplotlib

    Pero en realidad esta imagen estática no hace honor a un gráfico dinámico que se va recalculando sobre la marcha. Aquí os dejo un mini video con el resultado, y el programa completo:

    SDS011 3 plot

    ¡Accede al contenido!