Hojas de cálculo y Arduino

Al trabajar con arduino y los sensores una de la tareas mas recurrentes es poder registrar los valores que se registran para luego hacer algún tipo de análisis con ellos.

Esto resulta extremadamente sencillo si utilizamos un dispositivo que se conecte a la nube, como ESP8266 o el NodeMCU, puesto que con plataformas como thingspeak, es muy fácil, basta agregar algunas lineas de código a nuestro programa y todos los datos que se registren van a la base de datos de thingspeak y luego la podes descargar para analizarlo. Esto lo veremos más adelante en otro articulo.

La cosa cambia si estamos utilizando alguna versión mas modesta de Arduino (en nuestro caso utilizaremos un Arduno Nano) puesto que solo tenemos la salida por puerto serie y de alguna forma tenemos que capturar estos datos.

Para ello hacemos uso de un sencillo programa en Python, que abre el puerto serie, reconoce las columnas y guarda registro a registro en una planilla de calculo.

Puede descargar desde aquí:
https://github.com/gsampallo/serialToExcel

En el arduino tenemos un sencillo programa, que su única tarea es leer el valor del puerto analógico A0 e imprimirlo en el puerto serie. Es el siguiente:

int sensorPin = A0; 
int sensorValue = 0;

void setup() {
  Serial.begin(115200);
}

int i = 0;
void loop() {
  sensorValue = analogRead(sensorPin);
  Serial.print(i);
  Serial.print(",");
  Serial.print(sensorValue);
  Serial.println(";");
  i = i + 1;

  delay(10);
}

Se puede encontrar el archivo aquí, como ven es una modificación del ejemplo de Arduino para leer datos analógicos. Lo que se debe tener en cuenta es la forma en la que se envían los campos en el puerto serie, utilizar algún carácter para separar los campos, en el ejemplo utilizamos la coma.

Dentro del repositorio podemos encontrar un archivo llamado example.py que contiene un sencillo ejemplo de como utilizar la clase para guardar en un excel:

from serialToExcel import SerialToExcel

serialToExcel = SerialToExcel("COM36",115200)
serialToExcel.setColumns(["Nro Lectura","Valor"])
serialToExcel.setRecordsNumber(10)


serialToExcel.readPort()

serialToExcel.writeFile("archivo1.xls")

Como se puede ver es bastante sencillo, podemos dividirlo en tres etapas que se producen luego de importar la libreria:

  1. Se instancia la clase SerialToExcel, pasando como parámetros el puerto y la velocidad. También indicamos cuales son los nombres de las columnas y la cantidad de registros que deseamos guardar.
  2. readPort() comienza la lectura de datos desde el dispositivo. Hay que tener en cuenta que el proceso puede demorar, dependiendo de cada cuanto tiempo el dispositivo envie datos y la cantidad de registros que deseamos guardar.
  3. writeFile() guarda la planilla de calculo en el archivo cuyo nombre pasamos como argumento a la función.

El programa aún tiene mucho por mejorar, pero para realizar registros de datos es totalmente funcional.

Recuerden instalar los módulos solicitados:

pip install xlwt
pip install pyserial

Enviar correos con Python

Esta es la continuación de un articulo anterior donde generamos un reporte en excel, en esta entrada vamos a ver como enviamos dicho reporte por correo a una lista de destinatarios.

Necesitaremos los datos del servidor smtp para poder enviar los correos.

Sin entrar mucho en detalle, el contenido del mensaje de correo puede ser de tres tipos: texto plano, html o un archivo adjunto. En nuestro caso nos interesa enviar un html, para poder tener cierto formato y un archivo adjunto que sera el reporte que deseamos enviar.

Separamos en dos partes el programa para que sea mas sencillo de analizarlo y modificar a futuro.

Mensaje.py

Comenzaremos creando un archivo llamado Mensaje.py, contendrá la parte del mensaje en html; adicionalmente del constructor, tendrá dos métodos más: uno llamado agregarContenido() que nos permite agregar contenido dinámico y el otro getMensaje() que nos devuelve el html completo listo para enviarlo.

También contiene un atributo llamado subject , donde ira el asunto del mensaje.

class Mensaje(object):

    def __init__(self,inicio,fin):

        self.subject = "Reporte semanal de descansos "+inicio+" al "+fin

        self.head = "<html><h2><b>Reporte de Periodo "+inicio+" - "+fin+"</b></h2>"
        self.body = "<p>Se adjunta los datos de la semana.</p>"
            

        self.tail = '<p>Para datos especificos por favor referirse a la pagina <a href=\"#\">web</a>.</p></html>'

    def getMensaje(self):
        return self.head+self.body+self.tail
    

    def agregarContenido(self,data):
        self.body = self.body + data
    
     

ManejoMail.py

ManejoMail se ocupa de establecer la conexión con el servidor de correo; levantar los datos y enviar a la lista de destinatarios indicados.

Toma los parametros del servidor smtp, usuario y clave desde un archivo json llamado config.json con el siguiente formato:

{
    "smtp":"smtp",
    "mail":"demo@demo.com",
    "nombre":"UnserName",
    "puerto":"PortNumer",
    "password":"Password"
}

Finalmente ManejoMail tiene una lista como atributo interno llamado destinatarios, donde almacena la lista de los destinatarios que recibiran el correo.

import smtplib

from os.path import basename
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email.MIMEBase import MIMEBase
from email import Encoders
from Mensaje import Mensaje

import json
import io

class ManejoMail(object):

	def __init__(self):

		self.destinatarios = []
		self.parametros = json.load(open("config.json", "r"))


	def enviarCorreo(self,mensaje,archivo):

		smtp = smtplib.SMTP(self.parametros["smtp"])
		smtp.login(self.parametros["mail"], self.parametros["password"])

		correo = MIMEMultipart()
		
		correo['Subject'] = mensaje.subject
		correo['From'] = self.parametros["mail"]
		
		texto = MIMEText(mensaje.getMensaje(), 'html')
		correo.attach(texto)

		part = MIMEBase('application', "octet-stream")
		part.set_payload(open(archivo, "rb").read())
		Encoders.encode_base64(part)

		part.add_header('Content-Disposition', 'attachment; filename="'+archivo+'"')
		correo.attach(part)

		for destino in self.destinatarios:
			print ("Enviando correo a ",destino)
			correo['To'] = destino
			smtp.sendmail(self.parametros["mail"], destino, correo.as_string())

		

		smtp.quit()    

Si bien el listado de destinatarios lo almacenamos en una lista, tranquilamente podriamos leerlos desde una base de datos o de un archivo txt.

Cuestión 1: En este ejemplo y para no complicar tanto, los datos del servidor smtp y credenciales del usuario están dentro de un archivo json plano; podría ser prudente tomarlos desde una base de datos.

Cuestión 2: ManejoMail asume que siempre se va a enviar un archivo como adjunto; en caso que no sea lo que deseamos, se debe modificar las lineas donde se agrega el adjunto:

		part = MIMEBase('application', "octet-stream")
		part.set_payload(open(archivo, "rb").read())
		Encoders.encode_base64(part)

		part.add_header('Content-Disposition', 'attachment; filename="'+archivo+'"')
		correo.attach(part)

¿Como lo usamos?

Suponiendo que tenemos un archivo llamado ejemplo.xls, que deseamos enviar como adjunto y una lista con tres destinatarios, el uso seria el siguiente:

from ManejoMail import ManejoMail
from Mensaje import Mensaje

msg = Mensaje('01-06-2019','07-06-2019')
#Agregamos contenido al mensaje en formato html
msg.agregarContenido("<p>Agregamos un detalle al mensaje</p>")

#Agregamos una imagen al mensaje
msg.agregarContenido("<p><img src=\"http://www.gsampallo.com//wp-content/uploads/2019/07/logo-gs_logo-fondo-negro.jpg\" height=\"15%\"	width=\"15%\"></p>")

mMail = ManejoMail()

#Agregamos a la lista los destinatarios que deseamos que reciban el correo
mMail.destinatarios.append("destinatario1@demo.com")
mMail.destinatarios.append("destinatario2@demo.com")
mMail.destinatarios.append("destinatario3@demo.com")

mMail.enviarCorreo(msg,"ejemplo.xls")

Pueden encontrar el codigo completo del ejemplo en el repositorio de github:

https://github.com/gsampallo/automated_report_python

Un video de como funciona lo pueden ver acá: