Arduino enviando datos (Parte 2)

por Ricardo Vega el 15 de junio, 2016
Etiquetas:
ProgramaciónPythonM2MIoT
8 minutos.

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:

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

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

1pip 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:

1from flask import Flask
2from flask.ext.sqlalchemy import SQLAlchemy
3from flask_marshmallow import Marshmallow
4from flask.ext.restful import Api
5from flask_httpauth import HTTPBasicAuth
6
7# initialization
8app = Flask(__name__)
9app.config['SECRET_KEY'] = 'the quick brown fox jumps over the lazy dog'
10app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
11app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
12app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
13
14# api
15api = Api(app)
16
17# database
18db = SQLAlchemy(app)
19ma = Marshmallow(app)
20
21# security
22auth = HTTPBasicAuth()
23
24#users
25users = {
26 "admin": "password",
27 "ricveal": "1234"
28}

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.

1from datetime import datetime
2from app import db, ma
3
4class Sensor(db.Model):
5 id = db.Column(db.Integer, primary_key=True)
6 temperature = db.Column(db.Float())
7 date = db.Column(db.DateTime)
8
9 def __init__(self, temperature, date=None):
10 self.temperature = temperature
11 if date is None:
12 date = datetime.utcnow()
13 self.date = date
14
15
16class SensorSchema(ma.ModelSchema):
17 class Meta:
18 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:

1from app import db
2from sensor import Sensor
3db.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:

1from flask.ext.restful import Resource
2from flask import request, jsonify
3from app import app, api, db, auth, users
4from sensor import Sensor, SensorSchema
5
6schema = SensorSchema()
7
8@auth.get_password
9def get_pw(username):
10 if username in users:
11 return users.get(username)
12 return None
13
14class Index(Resource):
15 decorators = [auth.login_required]
16 def get(self):
17 return "Hello, %s!" % auth.username()
18
19class TemperatureList(Resource):
20 decorators = [auth.login_required]
21 def get(self):
22 allTemperatures = Sensor.query.all()
23 result = schema.dump(allTemperatures, many=True).data
24 return result
25 def post(self):
26 args = request.get_json()
27 sensor_read = Sensor(args['temperature'])
28 db.session.add(sensor_read)
29 return 'Temperature added', 200
30
31class Temperature(Resource):
32 decorators = [auth.login_required]
33 def get(self, id):
34 temperature = Sensor.query.get(id)
35 result = schema.dump(temperature).data
36 return result
37
38
39api.add_resource(Index, '/api/v1.0', endpoint='index')
40api.add_resource(TemperatureList, '/api/v1.0/temperature', endpoint='temperatures')
41api.add_resource(Temperature, '/api/v1.0/temperature/<string:id>', endpoint='temperature')
42
43if __name__ == '__main__':
44 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/<string:id>' 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:

1python 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:

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

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

¡Hasta la próxima semana!

Discutir en Twitter

Compartir artículo
Ricardo Vega es un desarrollador "full-stack" al que le gusta "cacharrear con todo" pero está especializado sobre todo en tecnologías Javascript, principalmente en React. Intenta devolver a Internet lo que Internet le ha dado.

Sigue leyendo 😀


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!

@ricveal

ricardo.vega@ricveal.com

Ricardo Vega