miércoles, 23 de abril de 2014

Arduino YÚN: invocando servicios web de AppEngine


Hoy he recibido mi Arduino YUN. Esta placa está basada en el Arduino Leonardo y tiene una serie de controladores y dispositivos WIFI, Ethernet y LINUX que nos permiten conectarlo a internet.

Así que qué mejor estreno para este nuevo juguetito que hacer una prueba conectando el Arduino a un servicio web RESTFul de App Engine.

Creación del servicio RESTFul


Comenzaremos creando el servicio RESTFul siguiendo el manual Servicios RESTFul con Jersey en App Engine.

Crearemos un objeto persistente com.demo.restful.model.ArduinoLog que almacenará un parametro (atributo param) y la fecha (atributo date).

package com.demo.restful.model;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.appengine.api.datastore.Key;

@Entity
@XmlRootElement
public class ArduinoLog implements Serializable {

         private static final long serialVersionUID = 1L;

         @Id
         private Key key;
         private String date;
         private String param;
        
         public ArduinoLog() {
                  super();
         }
         public Key getKey() {
                  return key;
         }
         public void setKey(Key key) {
                  this.key = key;
         }
         public String getDate() {
                  return date;
         }
         public void setDate(String date) {
                  this.date = date;
         }
         public String getParam() {
                  return param;
         }
         public void setParam(String param) {
                  this.param = param;
         }
}

A continuación su DAO (com.demo.restful.dao.ArduinoLogDAO) que tendrá solo el método create:

package com.demo.restful.dao;

import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import com.demo.restful.model.ArduinoLog;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class ArduinoLogDAO {
        
         private static EntityManagerFactory emf;
         //Singleton para devolver a EntityManager
         protected final EntityManager getEntityManager() {
                  synchronized (ArduinoLogDAO.class) {
                          if (emf == null) {
                                   emf = Persistence.
                                   createEntityManagerFactory("transactions-optional");
                          }
                  }
                  return emf.createEntityManager();
         }
        
         public ArduinoLog create(ArduinoLog arduinoLog) {
                  EntityManager mgr = getEntityManager();
                  try {
                          mgr.getTransaction().begin();
                          String oid = System.currentTimeMillis()
                                            + Long.toHexString((new SecureRandom()).nextInt());
                          Key key = KeyFactory.createKey(
                                            ArduinoLog.class.getSimpleName(),oid);
                          arduinoLog.setKey(key);
                          arduinoLog.setDate(
                                   new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").
                                            format(new Date()));
                          mgr.persist(arduinoLog);
                          mgr.getTransaction().commit();
                   } catch (Exception e) {
                           e.printStackTrace();
                  } finally {
                          mgr.close();
                  }
                  return arduinoLog;
         }
}

Y finalmente su Service y Servlet:

package com.demo.restful.service;

import com.demo.restful.dao.ArduinoLogDAO;
import com.demo.restful.model.ArduinoLog;

public class ArduinoLogService {

         public ArduinoLog create(ArduinoLog arduinoLog){
                  return (new ArduinoLogDAO()).create(arduinoLog);
         }
}

package com.demo.restful.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import com.demo.restful.model.ArduinoLog;
import com.demo.restful.service.ArduinoLogService;
import com.google.gson.Gson;

@Path("/arduino")
public class ArduinoLogServlet {
        
         protected final Gson gson = new Gson();
         protected final ArduinoLogService arduinoLogService = new ArduinoLogService();
        
         @GET
         @Path("/invoke/{param}")
         @Produces(MediaType.APPLICATION_JSON)
         public String create(@Context HttpServletRequest req,
                          @PathParam("param") String param){
                 
                  ArduinoLog arduinoLog = new ArduinoLog();
                  arduinoLog.setParam(param);
                 
                  String result = "";
                  try {
                          ArduinoLog arduinoLog2 = arduinoLogService.create(arduinoLog);
                          if (arduinoLog2!=null){
                                   result = gson.toJson(arduinoLog2);
                          } else {
                                   result = "Error creando el objeto";
                          }
                  } catch (Exception e) {
                          result = e.getMessage();
                  }
                  return result;
         }
}

Si ejecutamos nuestro proyecto, podremos probar invocando al servicio http://localhost:8888/rest/arduino/invoke/prueba con Postman.



Ahora, lo desplegaremos en Google App Engine, pero recordando configurar la aplicación en las propiedades del proyecto:



En este caso, yo he escogido arduino-yun-demo. Recuerda que son identificadores únicos a nivel de todo AppEngine y no puedes repetir el nombre.

Y ahora haremos la prueba contra AppEngine desde Postman cambiando la URL por la de nuestra aplicación, que en mi caso es https://ardunio-yun-demo.appspot.com/rest/arduino/invoke/prueba.


Creando el sketch para Arduino


Un detalle importante es que para trabajar con Arduino YUN necesitamos la versión 1.5 de su IDE. Por lo demás, partiré de la base de que ya teneis instalados los drivers de Arduino (solo hace falta en Windows) y configurada la placa para que se conecte a la WIFI según se indica en el manual oficial Guide to the Arduino Yún - Configuring the onboard WiFi.

En su IDE, crearemos un nuevo sketch:


A continuación incluiremos las librerías Bridge.h (para invocar al controlador que comunica nuestro chip de arduino ATmega con el chip de linux Linino) y HttpClient.h (cliente de http).

Después declararemos una variable gobal para identificar el led 13. Este led lo encenderemos cuando hagamos una invocación.

int led = 13;

Tras esto, en la función setup() abriremos el Bridge y el puerto serie para que el arduino nos devuelva por consola lo que ha leído.

void setup() {
  pinMode(led, OUTPUT);
  Bridge.begin();
  Serial.begin(9600);
  while (!Serial);
}

Y finalmente en la función loop() (la que se repite continuamente) escribiremos la invocación a nuestro servicio con un cliente HttpClient que simplemente escribirá en el puerto serie para que podamos ver por consola su contenido. Para indicar que estamos leyendo, usaremos el led rojo (pin 13) que encenderemos al principio y al final de la lectura http.

Y como se trata de un loop infinito, indicaremos que la función espere 30 segundos para cada invocación.

void loop() {
  Serial.println("-----------------------------------------");
  //indicamos que comenzamos a leer
  digitalWrite(13, HIGH);
  //definimos el cliente
  HttpClient client;
  //invocamos a nuestro WS
  client.get("http://ardunio-yun-demo.appspot.com/rest/arduino/invoke/prueba_arduino");
  //mientras tengamos datos escribimos
  while (client.available()) {
    char c = client.read();
    Serial.print(c);
  }
  Serial.println();
  Serial.flush();
  //y finalmente apagamos el led
  digitalWrite(13, LOW);
  //pedimos que espere 30s
  delay(30000);
}

Y a continuación compilamos y subimos al arduino por USB o WIFI:





Y para probarlo, abrimos el monitor serie (Herramientas > Monitor Serie) y esperamos a que escriba sus resultados.




Y listo. Podéis descargaros el proyecto Java de App Engine aquí y el sketch aquí.




ACTUALIZACIÓN: En este ejemplo hablo de como hacer un get a un servicio RESTFul desde Arduino YUN. En cambio, si queremos hacer un POST deberemos cambiar el código y usar los procesos de Linino.

Para ello, el  código quedaría tal que así:


void loop() {
  Serial.println("-----------------------------------------");
  //indicamos que comenzamos a leer
  digitalWrite(13, HIGH);
  //definimos el cliente
  HttpClient client;
  //invocamos a nuestro WS
  //GET
  /*client.print("{\"date\": \"1980-01-27 00:00:00\", \"param\": \"test post arduino\", \"body\": \"esto es un test de un post\"}");
  client.get("http://ardunio-yun-demo.appspot.com/rest/arduino/create");
   while (client.available()) {
    char c = client.read();
    Serial.print(c);
  }*/
  //POST
  Process p;
  p.runShellCommand("curl -X POST -H \"Content-Type: application/json\" -d '{\"date\": \"1980-01-27 00:00:00\", \"param\": \"post arduino\", \"body\": \"esto es un test de un post\"}' http://ardunio-yun-demo.appspot.com/rest/arduino/create");
  //mientras tengamos datos escribimos
  while (p.available()) {
    char c = p.read();
    Serial.print(c);
  }
  //finalizamos la escritura en el monitor
  Serial.println();
  Serial.flush();
  //y finalmente apagamos el led
  digitalWrite(13, LOW);
  //pedimos que espere 30s
  delay(50000);
}

OJO: En el servlet de ejemplo no está el método create (POST)