Arduino enviando datos (Parte 2)

por Ricardo Vega el 15/06/2016

Como ya te comenté en el post anterior, hoy vamos a continuar trabajando en el que será nuestro primer método para ser capaces de usar los datos que envía un Arduino de una forma más compleja. Como te adelantaba en ese post, vamos a montar una API REST que vamos a usar para añadir y recuperar valores de sensores de una base de datos y para ello, vamos a usar Python.

Montando una API REST con Python

La implementación que vamos a seguir en este post, guarda ciertas similitudes con el último post del curso que realizamos en este blog sobre Python, pero muchas cosas las hacemos de forma diferente. Puedes ver todo el código en github:

API REST con Python

Vamos a dividir la API en tres ficheros:

  • api.py : la API en sí. Aquí definimos la lógica a emplear al recibir peticiones, las URLs donde vamos a publicar nuestros recursos, etc. Es el núcleo de la aplicación.
  • app.py : diferentes valores de configuración e instancias genéricas que emplearemos en los demás ficheros. Aquí está todo centralizado.
  • sensor.py : en este fichero definiremos el modelo de datos que tiene el sensor. Crearemos nuestra entidad Sensor.

Mi recomendación es que hagas commit a mi repositorio de Github para iniciar el proyecto:

git clone https://github.com/ricveal/iot-rest.git
git checkout 25a6475

Una vez realizado esta tarea, podemos empezar a instalar todas las dependencias requeridas.

pip install -r /path/to/requirements.txt

Destaco entre todas las dependencias, las que considero más importantes:

  • Flask: ya lo conocemos de posts anteriores. Será nuestro framework.
  • Flask-HTTPAuth: extensión para securizar nuestra API.
  • Flask-RESTful: extensión que nos permite crear rápidamente APIs RESTful.
  • SQLAlchemy: ORM (nos permite generar modelos en python de la información que almacenamos en base de datos, generando una forma estandarizada de acceder a ésta mediante métodos tipo "find").
  • Flask-SQLAlchemy: extensión que integra Flask y SQLAlchemy.
  • marshmallow / marshmallow-sqlalchemy: sirve para serializar y deserializar nuestros modelos SQLAlchemy de forma que podamos trabajar más fácilmente en formato JSON.
  • flask-marshmallow: extensión que integra marshmallow con Flask.

Como ves, y ya adelantábamos en el anterior post, nuestra nueva API va a incluir opciones que la convierten en una opción mucho más completa que otros ejemplos que hemos realizado.

Vamos a empezar generando el corazón de nuestra aplicación, creando las instancias que usaremos en el resto de la misma:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask.ext.restful import Api
from flask_httpauth import HTTPBasicAuth

# initialization
app = Flask(__name__)
app.config['SECRET_KEY'] = 'the quick brown fox jumps over the lazy dog'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

# api
api = Api(app)

# database
db = SQLAlchemy(app)
ma = Marshmallow(app)

# security
auth = HTTPBasicAuth()

#users
users = {
    "admin": "password",
    "ricveal": "1234"
}

Como detalles, quiero que te fijes que hemos definido un diccionario 'users' con usuario - clave. Lógicamente, puedes modificarlo con los valores que quieras.

Procedemos ahora a definir nuestro modelo de datos. Para ello, el fichero sensor.py contiene lo siguiente.

from datetime import datetime
from app import db, ma

class Sensor(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    temperature = db.Column(db.Float())
    date = db.Column(db.DateTime)

    def __init__(self, temperature, date=None):
        self.temperature = temperature
        if date is None:
            date = datetime.utcnow()
        self.date = date


class SensorSchema(ma.ModelSchema):
    class Meta:
        model = Sensor

Definimos dos clases, la primera ("Sensor") sera el modelo que utilizará SQLAlchemy mientras que la segunda utiliza la primera para generar gracias a marshmallow un objeto que podemos convertir a JSON de forma sencilla. Si te fijas en la clase Sensor podrás ver que definimos un id, la temperatura y la fecha como atributos, especificando propiedades de la base de datos como el tipo de dato. Después simplemente, creamos un constructor que fija los parámentros e calcula la fecha date si esta no viene dada. El id se autogenera sin que hagamos nada ;).

Con estos dos ficheros creados, estamos en disposición de crear la base de datos con la estructura necesaria. Para ello, abrimos una consola Python y tecleamos los siguientes comandos:

from app import db
from sensor import Sensor
db.create_all()

Fíjate que se ha creado un fichero db.sqlite que podrás abrir con SQLite3.

Es el turno de crear ahora la API en sí, donde vamos a programar la lógica de la aplicación:

from flask.ext.restful import Resource
from flask import request, jsonify
from app import app, api, db, auth, users
from sensor import Sensor, SensorSchema

schema = SensorSchema()

@auth.get_password
def get_pw(username):
    if username in users:
        return users.get(username)
    return None

class Index(Resource):
    decorators = [auth.login_required]
    def get(self):
        return "Hello, %s!" % auth.username()

class TemperatureList(Resource):
    decorators = [auth.login_required]
    def get(self):
        allTemperatures = Sensor.query.all()
        result = schema.dump(allTemperatures, many=True).data
        return result
    def post(self):
        args = request.get_json()
        sensor_read = Sensor(args['temperature'])
        db.session.add(sensor_read)
        return 'Temperature added', 200

class Temperature(Resource):
    decorators = [auth.login_required]
    def get(self, id):
        temperature = Sensor.query.get(id)
        result = schema.dump(temperature).data
        return result


api.add_resource(Index, '/api/v1.0', endpoint='index')
api.add_resource(TemperatureList, '/api/v1.0/temperature', endpoint='temperatures')
api.add_resource(Temperature, '/api/v1.0/temperature/<string:id>', endpoint='temperature')

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Aquí es donde está la "chicha", así que iré algo más despacio.

En primer lugar, definimos el objeto schema a través de la clase que definimos anteriormente, para tenerla disponible en todo el fichero. Vamos a crear ahora la función que evaluará si una URL que definamos como segura, puede ser servida o no dependiendo de la información que vaya junto a la petición (en nuestro caso, usuario y password).

Concretamente, como usamos un método de usuario, contraseña, definimos un método que devuelva la contraseña esperada para el usuario que efectúa la petición, buscando dicha información en el diccionario 'users' que habíamos definido con anterioridad.

A partir de este momento, empezamos la API en sí:

Como ves, tenemos 3 clases, todas ellas extienden de Resource y es que al usar Flask-Restful, podremos generar la API de forma sencilla implementando cada URL como acciones a realizar sobre un recurso, siendo estas acciones las definidas por el protocolo HTTP.

Como dijimos en el anterior post, para este ejemplo nos limitaremos a GET para obtener información y POST para enviarla por lo que definiremos estos métodos dentro de cada clase.

Todas las clases llevan 'decorators = [auth.login_required]' para especificar que son recursos que requieren autentificación. En primer lugar tenemos 'Index' que simplemente acepta GET y devuelve un mensaje de saludo al usuario logueado. Los dos recursos "de verdad" son los siguientes:

Mediante 'TemperatureList', podemos obtener una lista de todos los valores almacenamos en base de datos haciendo uso de GET o añadir un nuevo valor mediante POST. Fíjate en la sintaxis que empleamos para recuperar los datos y convertirlos a un objeto JSON que podamos devolver, así cómo las instrucciones con las que añadimos información a la base de datos.

Algo parecido se repite en 'Temperature', pero en este caso, el recurso está pensado para obtener un valor específico de base de datos del que conocemos su identificador. Compara las diferencias a la hora de hacer la llamada a base de datos entre este recurso y el anterior. Comprueba también como no hemos introducido ni una sola instancia SQL, sino que empleamos métodos de la clase Sensor que se crearon automáticamente gracias a SQLAlchemy.

Ya sólo nos queda publicar estos recursos mediante api.add_resource(Nombre_Recurso, 'URL_Recurso', endpoint). Para definir las URLs, he seguido la forma más o menos estandarizada en cuanto a APIs, pero que para nada es obligatoria.

Quiero también que te fijes en '/api/v1.0/temperature/' donde estamos definiendo un valor en la propia URL (id que es un String) y como ese valor puede usarse directamente en los métodos que definimos en el recurso sin necesidad de hacer ningún parseo de la URL ni nada por el estilo, todo ocurre "automágicamente".

El último bloque, se encarga de levantar flask con todo lo que hemos configurado en nuestro ordenador pero permitiendo el acceso desde el exterior.

Para probar que todo ha ido como esperábamos, ejecuta:

python api.py

Debería arrancar el servidor sin problemas y quedar a la escucha de que contactemos con él.

La próxima semana, acabaremos este método construyendo el cliente que se ejecutará en Arduino y se comunicará precisamente con este API. Hasta entonces, puedes probar cosas mediante curl. Por ejemplo, accede al Index y comprueba la autentificación:

curl -u ricveal:1234 localhost:5000/api/v1.0/

Acuérdate de compartir esta entrada con tus amigos :)

¡Hasta la próxima semana!

Apoya al blog


Si te ha gustado este artículo, valora apoyarme económicamente a través de Patreon, una plataforma de Micro-mecenazgo con la que puedes hacerme un donativo que ayude a la continuidad del blog. Una pequeña ayuda significa mucho. 😃

Permanezcamos en contacto!


¿Quieres enterarte de todas las novedades del sector? ¿Te gustaría trabajar conmigo? ¡Puedes contactar conmigo de forma muy sencilla!