nano.png

El transceiver nRF24L01 es un módulo de comunicaciones muy conocido en el mundo de Arduino y los makers, común en aplicaciones de radio-control y drones. En esta entrada, veremos como interconectar un dispositivo WiFi (un smartphone) con un Arduino que emplea este transceiver (un Mowayduino), usando IDM y los endpoints de nRF24.

Requisitos

Para poder probar los ejemplos que aquí se proponen, necesitarás el siguiente hardware:

En cuanto al software, yo he utilizado Atom junto con PlatformIO, en Ubuntu 16.10. Si tu entorno es diferente, no debería ser muy difícil adaptar las instrucciones a tus circunstancias. Deja un comentario si tienes algún problema… 😉

También tendrás que instalar los siguientes paquetes (necesitarás añadir el repositorio Debian de pike, sigue las instrucciones que allí se indican):

$ sudo apt-get install idm ice-nrf24 python3-zeroc-ice icec

Sketch para el Mowayduino

Empezaremos por la parte más sencilla: el sketch que llevará tu Mowayduino (MWD). El objetivo será poder controlar el movimiento del coche, por lo que definiremos una interfaz Slice para los desplazamientos rectilíneos y los giros.

NOTA: Si no tienes un MWD a mano, y vas a utilizar un Arduino + nRF24, asegúrate de que el pinout es el correcto, modifica el sketch para quitar el código del MWD, e implementa el cuerpo de los métodos según tus necesidades.

Creación del Proyecto

Antes de nada, crea una carpeta (donde tú prefieras) para guardar el ejemplo que irás desarrollando al seguir este post.

$ mkdir nrf-example
$ cd nrf-example

Abre Atom, y en el Home Screen de PlatformIO, pulsa sobre ‘New Project‘. Si estás usando un Mowayduino, en ‘Selected board‘ debes poner ‘Arduino Leonardo’. Elige una carpeta donde guardar este proyecto, por ejemplo: ‘nrf-example/rover‘:

Screenshot from 2017-04-24 14-21-26

Lo siguiente que necesitas es descargar las librerías que vas a usar. En concreto, necesitarás lo siguiente:

Descomprime cada una de ellas en sus directorios, dentro de la carpeta ‘lib‘, que ha generado PIO. Para simplificar este proceso, puedes utilizar el script get-libs.sh: descárgalo en el directorio ‘lib‘ y ejecútalo:

lib$ wget -q https://goo.gl/NPo9id -O get-libs.sh
lib$ chmod a+x get-libs.sh && ./get-libs.sh

Definición de la Interfaz Slice

Dado que vas a utilizar Ice, es necesario que definas previamente la interfaz pública del MWD, utilizando el lenguaje Slice. En este ejemplo, podrás controlar el sentido del desplazamiento (forwardbackward), los giros (left right) y el freno (stop). Estos métodos son los que invocarás desde el cliente y que implementarás en el rover. Como no necesitas ningún parámetro adicional, la interfaz es muy sencilla:

module Example {
   interface Rover {
      void forward();
      void backward();
      void left();
      void right();
      void stop();
   };
};

Dado que este fichero está compartido entre el sketch del rover, y la aplicación de control, puedes guardarlo en la carpeta que creaste al inicio (‘nrf-example‘ en mi caso). Llámalo como quieras (siempre que respetes la extensión .ice), yo he elegido ‘rover.ice‘.

Esta interfaz Slice no se puede usar directamente desde el sketch de Arduino, antes es necesario ejecutar el translator de C (slice2c) que viene incluido en el paquete Debian de IceC. Para ello, en la carpeta ‘src‘, ejecuta lo siguiente:

$ slice2c ../../rover.ice

Esto generará un fichero llamado ‘rover.h‘, que sí podrás incluir en tu sketch. Recuerda que cada vez que modifiques el fichero Slice, tendrás que volver a ejecutar este comando para incluir las modificaciones en el fichero de cabecera generado.

Implementación del Servidor

La implementación de este sketch no es muy compleja, gracias a IceC y a la librería de Mowayduino. Para empezar, crea el fichero ‘main.cpp‘ en el directorio ‘src‘; es allí donde irás incluyendo todo el código de esta sección.

Lo siguiente que necesitas es añadir todas las librerías que vamos a usar: las propias de Arduino y Mowayduino, IceC, el endpoint de nRF24 y la interfaz del rover que acabas de generar:

#include <Arduino.h>
#include <EEPROM.h>
#include <SPI.h>
#include <RF24.h>

#include <IceC.h>
#include <nRF24Endpoint.h>
#include <MowayduinoRobot.h>

#include "rover.h"

A continuación, define algunas constantes que necesitas: los pines donde está conectado el bus SPI (CE_PIN y CS_PIN) la velocidad a la que se va a desplazar el rover (NORMAL_SPEED), y su dirección IDM (IDM_ADDRESS):

#define CE_PIN 0
#define CS_PIN 17
#define NORMAL_SPEED 100
#define IDM_ADDRESS "5F10"

Por último, antes de implementar los métodos setup() y loop(), es necesario definir algunas variables globales: el broker de IceC, un adaptador de objetos y un sirviente, junto con el robot de MWD:

Ice_Communicator ic;
Ice_ObjectAdapter adapter;
Example_Rover servant;
mowayduinorobot robot;

Para configurar todos esos objetos, utiliza la función setup() de Arduino. Lo que esta función debe hacer es:

  1. Inicializar el objeto robot (también he aprovechado para apagar todos sus LEDs).
  2. Inicializar el broker de IceC y el endpoint de nRF24.
  3. Crear el adaptador de objetos, con el endpoint de nRF24. En este caso, he usado el canal 90 (con la opción -c) y la dirección de RF ‘car01’ (con la opción -a).
  4. Registrar el sirviente de la interfaz Example::Rover.
  5. [opcional] indicar visualmente que el setup se ha completado.

Todo eso se traduce en el siguiente código:

void setup() {
   robot.beginMowayduino();
   robot.Ledsoff();

   // initialize Ice and Object adapter
   Ice_initialize(&ic);
   nRF24Endpoint_init(&ic, CE_PIN, CS_PIN);
   Ice_Communicator_createObjectAdapterWithEndpoints(
       &ic, "Adapter", "nrf24 -c 90 -a car01", &adapter);
   Ice_ObjectAdapter_activate(&adapter);

   // register Rover servant
   Example_Rover_init(&servant);
   Ice_ObjectAdapter_add(
       &adapter, (Ice_ObjectPtr)&servant, IDM_ADDRESS);

   robot.Greenon();
   delay(50);
   robot.Greenoff();
}

A continuación, debes implementar el bucle de eventos. En este ejemplo, no hay ninguna otra tarea que deba llevarse a cabo salvo esperar y procesar los mensajes de la red. Por ello, en la función loop() simplemente se ejecuta una iteración del broker:

void loop() {
   Ice_Communicator_loopIteration(&ic);
}

Ya sólo queda proporcionar la implementación de los métodos que se definieron en la interfaz: forward(), backward(), left(), right() y stop(). La firma exacta que debes usar se encuentra en el fichero generado (‘rover.h‘, busca las declaraciones __weak__). Para este ejemplo, podría servir la siguiente implementación:

void Example_RoverI_forward(Example_RoverPtr self) {
   robot.Straight(NORMAL_SPEED);
}

void Example_RoverI_backward(Example_RoverPtr self) {
   robot.Back(NORMAL_SPEED);
}

void Example_RoverI_left(Example_RoverPtr self) {
   robot.TurnLeft(NORMAL_SPEED);
}

void Example_RoverI_right(Example_RoverPtr self) {
   robot.TurnRight(NORMAL_SPEED);
}

void Example_RoverI_stop(Example_RoverPtr self) {
   robot.Stop();
}

Puedes compilar y cargar este firmware en el Mowayduino. Al arrancar deberías ver como parpadea rápidamente el LED superior, en color verde, lo que indica que está a la espera de tus órdenes 🙂

Router IDM

La clave para que este ejemplo funcione correctamente está en el router IDM, que se encarga de retransmitir las invocaciones Ice entre los diferentes dominios (en nuestro caso, entre nRF24 y WiFi). Para que el router pueda usar la red nRF24, se utiliza un endpoint diseñado a tal efecto. Este endpoint se comunica por USB con un Arduino que tiene cargado un fimware específico, y al que se conecta un transceiver nRF24L01+.

Para conectar el transceiver al Arduino (yo he usado un Arduino Nano, pero es posible utilizar cualquiera que sea compatible con UNO), sigue el siguiente esquema:

arduino-nano-rf24.png

También deberás cargar el firmware del endpoint dentro del Arduino. El código viene incluido como proyecto de PlatformIO dentro del paquete Debian del endpoint de nRF24. Cópialo en el directorio raíz de tu ejemplo y descarga las dependencias:

$ cp -r /usr/share/doc/ice-nrf24/ArduinoFirmware/ .
$ cd ArduinoFirmware/lib/ && sh get-libs.sh

Ahora, abre el proyecto con Atom y PlatformIO, compila y súbelo al Arduino Nano (o al que estés usando para el endpoint nRF24).

Para ejecutar el router, también necesitarás un fichero con la configuración apropiada que indique a Ice que debe cargar el endpoint de nRF24, y al router donde está la tabla de rutas. Crea un fichero en el directorio raíz de este ejemplo, junto al Slice, y ponle el nombre que prefieras (yo lo he llamado ‘router.config‘). Ábrelo e incluye lo siguiente:

Ice.Plugin.PyEndpoint = IcePyEndpoint:addPyEndpointSupport
PyEndpoint.Module = nRF24Endpoint
nRF24Endpoint.Device = /dev/ttyUSB0

Router.Adapter.Endpoints = nrf24 -c 90 -a idm : ws -p 1500
Router.Table.Path = router.table
Router.Ids = 5F01

Modifica esta configuración para que se adapte a tu situación. En mi caso, el Arduino con el transceiver para el endpoint estaba conectado en /dev/ttyUSB0, pero puede que no sea siempre así. Por otro lado, observa que el router tiene un endpoint WS, que será el que emplee la aplicación web de control.

En este ejemplo, no se hace uso de los anunciamientos automáticos en el router, por lo que es necesario proporcionar la tabla de rutas (‘router.table‘), con el siguiente contenido:

: destination | routerId | endpoints | mode
: --------------------------------------------------
5F10 | 5F10 | nrf24 -c 90 -a car01 | d

Por fin, puedes ejecutar el router con el comando:

$ idm-router --Ice.Config=router.config
WARNING:root: router controller not defined
WARNING:root: DUO advertisements disabled
INFO:root: queue Size: 10
INFO:root: router at '5F01 -t -e 1.1:nrf24 -c 90 -a idm:ws -h 192.168.0.150 -p 1500 -t 60000 -r /'
INFO:root: waiting events...

Aplicación de Control

Una vez que el router está funcionando, y que el Mowayduino tiene cargado el sketch que antes hemos descrito, sólo queda escribir el cliente que haga las veces de control remoto del rover. Se trata de una aplicación web estática que usará IceJS con HTML/CSS. Que sea estática evita la necesidad de un servidor web, y permite que pueda ser cargada directamente en cualquier sitio (concretamente, en un smartphone), simplemente copiando la carpeta correspondiente.

Dado que el cliente es más grande (que no más complejo) que el sketch, sólo describiré las partes relevantes, relativas básicamente a Ice. El cliente completo está en el repositorio de ice-nrf24.

index.html

En este fichero se encuentra el código HTML que da estructura a la aplicación. Básicamente carga las librerías de JS necesarias (IceJS, JQuery, Bootstrap, rover.js, app.js…), y define 4 botones, que se corresponden con los métodos para avanzar, retroceder, girar a la izquierda y girar a la derecha. Cuando se pulsa o suelta un botón, se llaman a los métodos correspondientes en ‘app.js‘.

rover.js

Este fichero se genera automáticamente al ejecutar el translator para JS de Ice sobre la interfaz del rover (‘rover.ice‘), con el siguiente comando:

slice2js ../rover.ice

app.js

Este fichero representa el core de la aplicación web. Básicamente se encarga de:

  1. Incializar Ice:
  2. $(document).ready(function() {
       ic = Ice.initialize();
       ...
  3. Crear el proxy al rover
  4. function connect() {
       var strprx = $('#rover-proxy').val();
       rover = ic.stringToProxy(strprx);
       rover = Example.RoverPrx.uncheckedCast(rover);
       rover = rover.ice_encodingVersion(Ice.Encoding_1_0);
    };
  5. Establecer los callbacks de los botones de la UI.
  6. $(document).ready(function() {
       ...
    
       $('#arrow-up').mousedown(forward).mouseup(stop);
       $('#arrow-down').mousedown(backward).mouseup(stop);
       $('#arrow-left').mousedown(left).mouseup(stop);
       $('#arrow-right').mousedown(right).mouseup(stop);
    });

El proxy del objeto remoto utiliza la dirección IDM del rover (5F10 en este caso) y los endpoints del router (ws -h 192.168.0.150 -p 1500):

5F10 -o:ws -h 192.168.0.150 -p 1500

La implementación de cada uno de los callbacks es muy similar. Básicamente llama al método correspondiente del proxy. Por ejemplo:

function forward() {
   if (rover == null) return show_error("rover is not started!");
   rover.forward().exception(show_error);
};

Una vez que la aplicación está lista, es posible abrirla directamente desde el navegador (sin necesidad de ejecutar ningún servidor web), y probar que el ejemplo funciona correctamente.

Referencias