Java ME 8 + Raspberry Pi + Sensores = Un Mundo IoT (Parte 1)

Por Jose Cruz
Publicado en Septiembre 2015

Conozca como conectar sensores al Raspberry Pi y a controlarlos con Java.

La última versión de Java ME 8 incluye un poderoso API para controlar dispositivos como LEDs, relays, LCDs, sensores, motores, y switches.

Este articulo es el primero de una serie de cuatro relacionado con como conectar sensores electrónicos al Raspberry Pi Modelo B usando interfaces general-purpose input/output (GPIO), inter-integrated circuit bus (I2C), serial peripheral interface bus (SPI), o universal asynchronous receiver/transmitter (UART).

Utilizando Java ME 8 para controlar dispositivos con diferentes tipos de interfaces y conectándolos al Raspberry Pi podemos crear un mundo de Internet de las Cosas (IoT).

Este artículo se enfoca en el uso del GPIO y muestra ejemplos de cómo desarrollar clases en Java ME 8 que pueden

Nota: Los ejemplos de código están en un proyecto NetBeans IDE 8.0 y pueden ser descargados aquí.

API Device I/O
La especificación Device I/O API define un API de I/O para dispositivos periféricos genéricos,  para aplicaciones Java ejecutándose en pequeños dispositivos embedded. Define APIs para algunos de los más comunes dispositivos incluyendo los siguientes:

  • GPIO
  • I2C
  • SPI
  • Convertidores Analógico a Digital (ADCs)
  • Convertidores Digital a Analógico (DACs)
  • UART
  • Mapeo de memoria vía I/O (MMIO)
  • Comandos AT
  • Temporizadores Watchdog
  • Contadores de Pulsos
  • Moduladores de ancho de pulso (PWM)
  • Dispositivos genéricos

Circuitos que crearemos

Un dispositivo GPIO puede utilizar tanto una entrada como una salida digital, esta puede ser habilitada o deshabilitada, o puede ser manipulada mediante manejo de interrupciones. Sin embargo es importante considerar que todos los pines GPIO del Raspberry Pi operan a 3.3v. Por lo tanto es muy importante verificar las especificaciones técnicas de los dispositivos que deseen conectar para determinar si estos utilizan 3.3 V o 5 V. En algunos casos es importante utilizar un convertidor de niveles lógicos como este.

Basado en el diagrama de bloques mostrado en la Figura 1 y los componentes mostrados en la Figura 2, crearemos los circuitos mostrados en la Figura 3.

Figure 1. Diagrama de bloques de los circuitos que vamos a crear


Figure 2. Componentes de vamos a utilizar


Figure 3. Esquema de los circuitos que vamos a crear

Conectando el detector de llama

El sensor de llama de DFRobot DFR0076 puede ser utilizado para detectar fuego o longitudes de luz entre aproximadamente 760 nm and 1100 nm. Lo podemos conectar a 3.3 V o 5 V, y el rango de detección es aproximadamente 20 cm (4.8 V) hasta 100 cm (1 V). Cuando detecta fuego el activa con un nivel lógico alto el pin de señal.

Conectemos el sensor de llama a los pines de 3.3 V, Gnd y GPIO 22 del Raspberry Pi, como muestra la Figura 3 y creemos una clase en Java ME 8 para controlar este sensor.

Primero, crear una clase DFR0076Device que utiliza el API Device Access, y definir una variable pin
que soporte la interface hacia el GPIO, como lo muestra la Listado 1.

public class DFR0076Device {

private GPIOPin pin = null;  //Define el pin para el control de llama

Listado 1. Clase para el control del sensor detector de llama.

A continuación, crear una clase constructor que inicialice y active el pin GPIO 22 usando el API DeviceManager y la clase GPIOPinConfig (ver Listado 2) estableciendo las siguientes condiciones:

 

  • Nombre dispositivo: 0
  • Número del pin: GPIO 22 (especificado mediante pinGPIO)
  • Dirección: input only (solo entrada)
  • Modo: pull-up (se activa en alto, 1 o positivo)
  • Disparador: rising-edge (detecta el cambio hacia positivo o 1)
  • Valor inicial: false
   
  public DFR0076Device(int pinGPIO) { 
  ...
  pin = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
  0, pinGPIO,GPIOPinConfig.DIR_INPUT_ONLY,GPIOPinConfig.MODE_INPUT_PULL_UP,
  GPIOPinConfig.TRIGGER_RISING_EDGE, false));
  ...
  }
  


Listado 2. Establecer condiciones iniciales

Ahora, crear un método que reciba una clase tipo manejador de eventos que soporta los eventos de detección de llama como lo muestra el Listado 3.

public void setListener(PinListener flameListener) {
  ...
  if (pin!=null)
  pin.setInputListener(flameListener);
  ...
  }

Listado 3. Método que soporta eventos al detector llama

Es muy importante cerrar el pin cuando no se necesite y/o utilice, y liberar el manejador de eventos asignado a ese pin, como lo muestra el Listado 4.

public void close() {
  ...
         if (pin!=null){
  pin.setInputListener(null);
              pin.close();
         }
  ...
  }

Listado 4. Cerrando el pin y liberándolo del manejador de eventos

Ahora, crear la clase principal MIDlet que invocará nuestro código y define la clase que procesará los eventos del sensor de detección de llama, como lo muestra el Listado 5.

  public class TestSensors extends MIDlet {
  DFR0076Device flame;
  private static final int FLAME_DETECTOR_PIN = 22;
     public void startApp() {
  //Inicializar el sensor de llama
  flame = new DFR0076Device(FLAME_DETECTOR_PIN);
  flame.setListener(new FlameSensor());
  }
     public void destroyApp(boolean unconditional) {
  flame.close();
  }
    private static int waitnext = 1;
  class FlameSensor implements PinListener {
      public void valueChanged(PinEvent event) {
  if (event.getValue() && --waitnext == 0) {
  System.out.println("Advertencia Fuego detectado!!!");
  waitnext = 10;
  }
               }
          }
  }

Listado 5. Creando el MIDlet para invocar nuestro código

Conectando el detector de movimiento

Ahora vamos a añadir la funcionalidad de detección de movimiento a nuestro MIDlet TestSensors. Para eso vamos a necesitar un sensor como el HC-SR501 mostrado en la Figura 2.

Los sensores PIR nos permiten detectar movimientos. Todo emite una pequeña cantidad de radiación infrarroja, y cuanto más caliente más radiación se emite. Los sensores PIR están adecuados para detectar cambios en los niveles infrarrojos en el rango o zona de acción, por ejemplo cuando un humano ingresa a una habitación.

Los sensores PIR utilizan tres pines: GND, salida digital y 3 a 5 V. En reposo, cuando no se detecta movimiento la salida digital se mantiene baja. Sin embargo cuando el movimiento es detectado, la salida digital emite un pulso alto (3.3 V), por lo que nos permite conectar el sensor directamente al Raspberry Pi.

El sensor PIR tiene un puente utilizado para pruebas (ver Figura 4).

  • Colocando el puente en la posición “L” crea una activación simple sin repetición. La salida se mantiene baja durante 3 segundos después que el movimiento fue detectado.
  • Colocando el puente en la posición “H” crea una activación repetitiva mientras detecta movimiento pero se desactiva enseguida que el movimiento no es detectado.

Adicional es posible hacer los siguientes ajustes en los potenciómetros laterales:

  • Ajustando el potenciómetro de la sensibilidad en la dirección de las agujas del reloj incrementa la sensibilidad para una distancia cercana a los 7 metros, en sentido contrario la sensibilidad disminuye a una distancia cercana a los 3 metros.
  • Ajustando el potenciómetro del tiempo de espera en la dirección de las agujas del reloj incrementa el intervalo entre mediciones hasta los 300 segundos, en sentido contrario la acorta hasta un mínimo de 5 segundos.


Figura 4. Puente de prueba y potenciómetros

Ahora conectemos  el sensor PIR al Raspberry Pi pines 5v, GND y GPIO 24, como muestra la Figura 3, y creamos la clase en Java ME 8 HCSR501Device para el control utilizando el API Device Access, como se muestra en el Listado 6.

public class HCSR501Device {
private GPIOPin pin = null;

Listado 6. Clase HCSR501Device

Enseguida creamos la clase constructor que inicializa y activa el pin GPIO24 usando el API DeviceManager y la clase GPIOPinConfig (ver Listado 7) para establecer las siguientes condiciones:

  • Nombre dispositivo: 0
  • Número del pin: GPIO 24 (especificado mediante pinGPIO)
  • Dirección: input only (solo entrada)
  • Modo: pull-down (se activa en bajo)
  • Disparador: rising-edge (detecta el cambio hacia positivo o 1)
  • Valor inicial: false
  • Esperar tres segundos que se inicialice el sensor PIR
public HCSR501Device(int pinGPIO) {
  ...
  pin = (GPIOPin) DeviceManager.open(new GPIOPinConfig(
  0, pinGPIO, GPIOPinConfig.DIR_INPUT_ONLY, GPIOPinConfig.MODE_INPUT_PULL_DOWN,
  GPIOPinConfig.TRIGGER_RISING_EDGE, false));
  I2CUtils.I2Cdelay(3000);    //wait for 3 seconds
  ...
 }

Listado 7. Estableciendo condiciones iniciales

Ahora, se crea un método que reciba una clase que maneje eventos para soportar el evento causado por la detección de movimiento, como lo muestra el Listado 8.

public void setListener(PinListener pirListener) {
  ...
  if (pin!=null)
  pin.setInputListener(pirListener);
  ...
  }

Listado 8. Método que captura los eventos provenientes de la detección de movimiento.

Es importante cerrar el manejo del pin cuando no se utilice y asegurarse de liberar el pin del manejador de eventos al cual se asoció anteriormente, como lo muestra el Listado 9.

public void close() {
  ...
  if (pin!=null){
  pin.setInputListener(null);
  pin.close();
  }
  ...
  }


Listado 9. Cerrando el pin y liberándolo del manejador de eventos.

Extendemos nuestra clase MIDlet para soportar el sensor PIR y su manejador de eventos, como lo muestra el Listado 10.
  
  //Define el objeto del sensor HCSR501
  HCSR501Device pir;
  private static final int MOTION_DETECTOR_PIN = 24;
  
  @Override
  public void startApp() {
  ...    
  //Inicializa el sensor PIR
  pir = new HCSR501Device(MOTION_DETECTOR_PIN);
  pir.setListener(new PirSensor());
  ...    
 }
    @Override
  public void destroyApp(boolean unconditional) {
  ...
  pir.close();
  ...
  }
  
  //Chequea el sensor PIR para detector movimiento
  class PirSensor implements PinListener {
        @Override
  public void valueChanged(PinEvent event) {
  if (event.getValue()) {
  System.out.println("Advertencia movimiento detectado!!!");
  }
       }
   }

Listado 10. Extendiendo la clase MIDlet para soportar el sensor PIR

Conectado el Sensor de Distancia

El HC-SR04 es un detector de rango ultrasónico que utiliza el sonar para determinar la distancia con los objetos, como lo hacen los murciélagos y delfines.

Viene con el modulo transmisor y el módulo receptor y tiene las siguientes características:

  • Alimentación :+5 Vdc
  • Consumo de corriente: 15 mA
  • Angulo eficaz: <15 grados
  • Rango de distancia : 2 cm–400 cm/1 inch–13 feet
  • Resolución : 0.3 cm
  • Angulo de medida: 30 grados
  • Ancho de pulso: 10 µs

Para comenzar la medida, el pin de disparo del HC-SR04 debe recibir un pulso alto (5 V) al menos por 10 µs, lo cual inicializa el sensor y causa que este emita una ráfaga de ultrasonidos durante ocho ciclos a una frecuencia de 40 kHz y espera que el receptor reciba los ultrasonidos enviados y reflejados.

Cuando el receptor detecta la ráfaga de ultrasonidos, coloca en alto (5 V) el pin “echo” y espera por un lapzo proporcional a la distancia medida.
Para calcular la distancia se utiliza la siguiente fórmula, donde “la velocidad del sonido esta expresada en cm/sec” igual a 34029 cm/sec:

Distancia (en cm) = ((Duración en ns del nivel alto del pin of “echo”)*(velocidad del sonido en cm/sec))/ 2 /1000000000 ns

Como se mencionó este sensor opera con 5 V, por lo que la señal de disparo puede ser activada por los 3.3 V del pin GPIO 23 sin problema.

Para el caso de la lectura del nivel de la señal “echo”, cuando se active, el sensor envía 5 V, lo cual no es soportado por el máximo de voltaje a leer en el pin GPIO 17 del Raspberry Pi, por lo que será necesario emplear un divisor de voltaje que convierta la señal de 5 v a 3.3 V.

En la Figura 5 se muestra cómo se puede construir este divisor de voltaje utilizando dos resistencias una de 330 ohm y la otra de 470 ohm para establecer un voltaje de 2.9 V en el pin GPIO 17. Puede utilizar este divisor de voltaje en línea como herramienta para calcular Vout basado en las dos resistencias.

Figura 5. Circuito para el divisor de voltaje

Es hora de crear la clase Java ME 8 llamada HCSR04Device para controlar el sensor HC-SR04 y enviar un pulso que mida la distancia en centímetros, ver Listado 11.

public class HCSR04Device {
    private final int PULSE = 10000;        // #10 µs pulso = 10,000 ns
  private final int SPEEDOFSOUND = 34029; // Velocidad del sonido = 34029 cm/s
    private GPIOPin trigger = null;
  private GPIOPin echo = null;

Listado 11. Creando la clase HCSR04Device

Se crea la clase constructor que inicializa y active el disparador GPIO y el pin “echo” utilizando el API

DeviceManager y la clase GPIOPinConfig para establecer las siguientes condiciones (ver Listado 12)

  • Nombre dispositivo: 0
  • Número del pin: disparador = GPIO 23 y “echo” = GPIO 17 (especificado mediante _trigger y _echo)
  • Dirección: trigger = output only, echo = input only
  • Modo: trigger = output push-pull, echo = input pull-up
  • Disparador: none para ambos
  • Valor inicial: false
  • Esperar 0,5 segundos antes de inicializar el HC-SR04
public HCSR04Device(int _trigger, int _echo) {
  ...
  trigger = (GPIOPin) DeviceManager.open(new GPIOPinConfig(0, _trigger, 
  GPIOPinConfig.DIR_OUTPUT_ONLY, GPIOPinConfig.MODE_OUTPUT_PUSH_PULL,
  GPIOPinConfig.TRIGGER_NONE, false));
  
  echo = (GPIOPin) DeviceManager.open(new GPIOPinConfig(0, _echo, 
  GPIOPinConfig.DIR_INPUT_ONLY, GPIOPinConfig.MODE_INPUT_PULL_UP, 
  GPIOPinConfig.TRIGGER_NONE, false));
       I2CUtils.I2Cdelay(500);  //Esperar 0.5 seconds
  ...
   }

Listado 12. Estableciendo condiciones iniciales

El método pulse calcula la distancia en centímetros cambiando el estado del pin del disparador de 1 a 0 durante 10 µs (10,000 ns), computando el tiempo que el pin “echo” cambia de 0 a 1, todo en nanosegundos, como se muestra en el Listado 13.

public double pulse() {
  long distance = 0;
  try {
  trigger.setValue(true); //Enviar un pulso trigger; debe ser 1 y 0 con 10 µs espera
  I2CUtils.I2CdelayNano(0, PULSE);// espera 10 µs
  trigger.setValue(false);
  long starttime = System.nanoTime(); //ns
  long stop = starttime;
  long start = starttime;
  //echo ira de 0 a 1 y necesitamos guarder el tiempo. 2 segundos de diferencia
  while ((!echo.getValue()) && (start < starttime + 1000000000L * 2)) {
  start = System.nanoTime();
  }
  while ((echo.getValue()) && (stop < starttime + 1000000000L * 2)) {
  stop = System.nanoTime();
  }
  long delta = (stop - start);
  distance = delta * SPEEDOFSOUND; // echo de 0 a 1 dependiendo de la distancia del objeto
  } catch (IOException ex) {
  Logger.getGlobal().log(Level.WARNING,ex.getMessage());
  }
  return distance / 2.0 / (1000000000L); // cm/s
  }

Listado 13. Método para calcular distancia

Finalmente, liberamos todos los recursos cerrando ambos pines (ver Listado 14).
  
  public void close() {
  ...
  if ((trigger!=null) && (echo!=null)){
  trigger.close();
  echo.close();;
  }
  ...
  }
 

Listado 14. Cerrando ambos pines
Extendemos la clase MIDlet para soportar el sensor HC-SR04 y calcular la distancia, como se muestra en el Listado 15.

public class TestSensors extends MIDlet {
  //Define el objeto HCSR04 
  HCSR04Device hcsr04;
  private static final int TRIGGER_PIN = 23;
  private static final int ECHO_PIN = 17;
  
  //Define el thread que leera el sensor
  private volatile boolean shouldRun = true;
  private ReadSensors sensorsTask;
  
  @Override
  public void startApp() {
  ...
  //Inicializar el sensor ultrasonico
  hcsr04=new HCSR04Device(TRIGGER_PIN, ECHO_PIN);
  //Start read sensors data thread
  sensorsTask=new ReadSensors();
  sensorsTask.start();
 }
    @Override
  public void destroyApp(boolean unconditional) {
  shouldRun=false;
  ...
  hcsr04.close();
  }
  
  // Thread para leer la distancia cada 5 segundos
  class ReadSensors extends Thread {
  private double distance=0.0;
  
  @Override
  public void run() {
  while (shouldRun){
  distance = hcsr04.pulse();
  if (distance>0) 
  System.out.println("Objecto detectado a " + distance + " cm.");
  I2CUtils.I2Cdelay(5000);
  }
  }
  }
  }

Listado 15. Extendiendo la clase MIDlet para soportar el sensor HC-SR04 y calcular la distancia

Antes de ejecutar el MIDlet utilizando NetBeans IDE 8.0, es importante establecer los permisos del API. Para hacer esto seleccionamos el proyecto JavaMEDemos, click derecho y seleccionamos Properties para mostrar la ventana de propiedades del proyecto, seleccionamos Application Descriptor, y la pestaña API Permissions. Incluir los cuatro permisos, mostrados en la Figura 6:

  jdk.dio.DeviceMgmtPermission *:* , open
  jdk.dio.gpio.GPIOPinPermission *:* , open, setdirection
  jdk.dio.gpio.GPIOPortPermission *:* , open
  java.util.logging.LoggingPermission control, null

Figura 6. Establecer los permisos para el API

Ejecutando el MIDlet en un Emulator Utilizando NetBeans IDE 8

Si no tiene un Raspberry Pi pero desea probar el MIDlet, NetBeans IDE 8.0 tiene un emulador de dispositivos en el cual puede crear uno adaptado a la configuración con los pines requeridos.

GPIO 17: Input
GPIO 22: Input
GPIO 23: Output
GPIO 24: Input

Podemos configurar el emulador definiendo estos pines y probar nuestro MIDlet antes de cargar el código Java en el Raspberry Pi.

Primero, seleccione Tools > Java ME > Custom Device Editor para abrir la ventana del Custom Device Editor, mostrado en la Figura 7.



Figura 7. Ventana Custom Device Editor

Seleccione MEEP y haga click en New para crear un nuevo dispositivo customizado llamado MEEPCustomDevice, el cual se mostrara en la ventana Edit MEEP Device, mostrada en la Figura 8.



Figure 8. Ventana Edit MEEP Device

En la pestaña GPIO, cree los pines GPIO necesarios y especifique su configuración. Cuando esté listo haga click en el botón OK

En el IDE NetBeans, aparecerá el nuevo dispositivo, MEEPCustomDevice, en la ventana Device Selector.

 


Figura 9. Ejecutando el MIDLet con MEEPCustomDevice

Seleccione la pestaña GPIO Pins para ver todos los pines GPIO (Figura 10) que configuro en la ventana Edit MEEP Device (Figura 8).



Figura 10. Examinando los pines GPIO

Seleccionando Tools > External Events Generator abrirá la ventana que se muestra en la Figura 11. En esta ventana puede presionar los botones para controlar los pines GPIO 17, GPIO 22, and GPIO 24 para emular el sensor de distancia, el sensor de llama y el sensor de movimiento respectivamente. Adicional en la consola puede observar el log donde se despliega los eventos detectados, como lo muestra la Figura 12.



Figura 11. Ventana External Events Generator



Figura 12. Log de los eventos detectados

Conclusión

Cada dispositivo tiene sus especificaciones técnicas, las cuales deben ser revisadas en detalle antes de decidir a qué tipo de interface se puede conectar. En particular es importante determinar para cual voltaje  esta designado, para asegurar la vida de su Raspberry Pi y de cada dispositivo.

La interfaz GPIO facilita la conexión de sensores que pueden ser utilizados con entradas y salidas digitales, que puedan ser habilitadas y deshabilitadas y que puedan ser utilizadas como líneas de interrupciones. Para cada dispositivo es importante definir una clase Java ME 8 que maneje las transacciones requeridas y contenga la lógica de control necesaria, como por ejemplo las clases DFR0076Device, HCSR501Device, and HCSR04Device que nosotros creamos para el detector de llama, detector de movimiento y sensor de distancia.

Creando MIDlets, puede fácilmente desplegar aplicaciones en el Raspberry Pi y realizar todo tipo de experimentos, para lo cual solo la imaginación es el límite. Si no posee un Raspberry Pi pero desea emularlo, NetBeans IDE 8 es un ambiente de desarrollo Java poderoso y le permite crear dispositivos virtuales y experimentar con interfaces que Ud defina.

En próximos artículos de esta serie, examinaremos otros tipos de sensores- como sensores que detecten temperatura, presión y niveles de luz- utilizando otro tipo de interfaces como lo son I2C, UART y SPI.

Puede continuar con la segunda entrega de esta serie aquí.

Lecturas Sugeridas


Jose Cruz (@joseacruzp) es un ingeniero de software quien ha estado trabajando con Java desde 1998. Es el líder de desarrollo de aplicaciones Java, Java ME y Java EE en Ferretería EPA C.A. en Venezuela. Desde temprana edad su hobbie ha sido la electrónica. Esto le ha permitido combinar la computación con la electrónica y desarrollar proyectos donde Java y los dispositivos embebidos como Arduino y Raspberry Pi son protagonistas.

Este artículo ha sido revisado por el equipo de productos Oracle y se encuentra en cumplimiento de las normas y prácticas para el uso de los productos Oracle.