Escornabot: Control por gestos

Introducción

En este tutorial vamos controlar nuestro querido Ecornabot mediante gestos.

 

Para este proyecto utilizaremos la placa Arduino Nano 33 BLE Sense que tiene el módulo APDS-9960 integrado. Este módulo posee un sensor de gestos, un sensor de proximidad, un sensor de color RGB y un sensor de luz. Además esta placa Arduino Nano 33 BLE Sense tiene bluetooth integrado gracias al módulo NINA B306 (con calificación de Bluetooth 5.0). 

Por tanto tenemos en una sola placa todo lo necesario para realizar de una forma sencilla nuestro proyecto.

Circuito

El circuito será muy sencillo y a parte del Escornabot necesitaremos los siguientes componentes:

    • Dispositivo central (o master):  Arduino Nano 33 BLE Sense. 
    • Dispositivo periférico (o esclavo):  Módulo HM-10 Bluetooth 4.0 BLE (en mi caso de la marca DSD TECH) conectado al Escornabot.

La alimentación del dispositivo central Arduino Nano 33 BLE Sense la podemos realizar o mediante un cable USB (opción utilizada en este tutorial) o conectando una fuente de alimentación entre los pines GND y Vin de la propia placa. 

NOTA: El rango de voltaje que soporta la placa Arduino Nano 33 BLE Sense en el pin Vin es de 4.5V a 21V con lo cual con 4 pilas AA sería suficiente.

NOTA: El módulo HM-10 lo conectaremos al Escornabot y es conveniente utilizar un divisor de tensión para no dañar el dispositivo si utilizamos la versión DIY. Para las versiones Compactus y Singularis esto no es necesario ya que dispone de un conector específico  con un divisor de tensión ya integrado.

Obteniendo los UUIDS

La comunicación entre el dispositivo central (o master) Arduino Nano 33 BLE Sense y el dispositivo periférico (o esclavo) HM-10 Bluetooth 4.0 BLE conectado al Escornabot se realizará mediante bluetooth, concretamente mediante  la tecnología Bluetooth Low Energy.

En los siguientes enlaces se explican a los conceptos básicos de la tecnología Bluetooth Low Energy  que nos ayudará a entender su funcionamiento (sobre todo entender los conceptos de servicios y características).

https://www.arduino.cc/en/Reference/ArduinoBLE

https://learn.adafruit.com/introduction-to-bluetooth-low-energy/introduction

Una vez entendido los conceptos básicos de la tecnología Bluetooth Low Energy que se explican en los enlaces anteriores vamos a explicar qué ocurrirá en nuestro proyecto:

    • El dispositivo central se conectará al dispositivo periférico buscando la característica del servicio especificado. 
    • Una vez establecida la conexión si el sensor de gestos del Arduino Nano 33 BLE Sense (ADPS-9960) detecta cualquiera de los siguientes movimientos: ARRIBA, ABAJO, IZQUIERDA, DERECHA, EJECUTAR o RESETEAR  el dispositivo central escribirá el valor correspondiente (instrucción del movimiento asociado a estos gestos) en la característica del servicio del dispositivo periférico haciendo que el robot reciba la instrucción correspondiente.

Para ello lo primero que tenemos que hacer es identificar qué servicio y qué característica tiene nuestro dispositivo periférico HM-10 para que nuestro dispositivo central se pueda conectar y enviarle instrucciones.

    • Para ello conectaremos el módulo HM-10 al Escornabot y activaremos el robot.
    • Utilizaremos la aplicación LightBlue® Explorer ( o equivalente) para vincular el módulo HM-10 a nuestro móvil y descubrir el servicio y característica que nos interesa para nuestro propósito. 

    • Una vez detectado el dispositivo, nos conectaremos a él y veremos que dentro del apartado de GATT SERVICES & CHARACTERISTICS dispone del servicio con el identificador 0000ffe0-0000-1000-8000-00805f9b34fb que es el que nos interesa:

    • Dentro de ese servicio veremos que existe la característica con el identificador 0000ffe1-0000-1000-8000-00805f9b34fb que es la que nos interesa y que tiene propiedades de escritura y lectura ( en nuestro proyecto solo utilizaremos la propiedad de escritura para enviar las instrucciones al robot):

    • En este punto podríamos desde la propia aplicación enviar instrucciones al robot para comprobar su funcionamiento (Ejemplo: Escribir n\n  dentro del apartado WRITTEN VALUES y darle a WRITE→ esta es la instrucción de ir hacia adelante eligiendo previamente en “Data format” → UTF-8 String.)

En este punto ya tenemos la información de los identificadores del servicio y de la característica del módulo esclavo para que el módulo central se pueda conectar y enviarle instrucciones. En este caso son los siguientes:

Servicio UUID:  0000ffe0-0000-1000-8000-00805f9b34fb

Característica UUID: 0000ffe1-0000-1000-8000-00805f9b34fb

En realidad solo serán necesarios los identificadores marcados en negrita prescindiendo del resto:

Servicio UUID:  ffe0

Característica UUID: ffe1

Estos identificadores son los típicos que os vais a encontrar en vuestros módulos HM-10 y son los que ya se encuentran incluidos en el sketch que cargaremos en el Arduino Nano 33 BLE Sense pero si fueran diferentes con la operativa anteriormente descrita los podéis obtener.

Programación

En este apartado tenéis el Skecth a cargar en el dispositivo central Arduino Nano 33 BLE Sense. También se encuentra en el siguiente repositorio:

https://github.com/avilmaru/escornabot_control_gestual

NOTA: En caso que vuestros identificadores de servicio y característica sean diferentes a los que contiene el Skecth debéis seguir la operativa que se explica en el apartado anterior para obtener vuestros identificadores y cambiarlos en el Skecth.

    • const char* deviceServiceUuid = «ffe0»;
    • const char* deviceServiceCharacteristicUuid = «ffe1»;

NOTA: Todas las trazas se encuentran comentadas en el Skecth pero podéis descomentarlas si queréis ver por el puerto serial las distintas trazas que se van generando.

NOTA: Este Skecth utiliza las siguientes librerías:

    • #include <ArduinoBLE.h>
    • #include <Arduino_APDS9960.h>

Estas librerías las  podéis obtener desde el IDE de Arduino (Gestión de librerías).

// ---------------------------------------------------------------------------
// Escornabot: Control por gestos - v1.0 - 18/10/2019
//
// AUTOR:
// Creado por Angel Villanueva - @avilmaru
//
// LINKS:
// Blog: https://mecatronicalab.es
//
//
// HISTORICO:
// 18/10/2019 v1.0 - Release inicial.
//
// ---------------------------------------------------------------------------
  

#include <ArduinoBLE.h>
#include <Arduino_APDS9960.h>

const int R_LED_PIN = 22;
const int G_LED_PIN = 23;
const int B_LED_PIN = 24;

const char* deviceServiceUuid = "ffe0";
const char* deviceServiceCharacteristicUuid = "ffe1";

int gesture = -1;
int lastGesture = -1;
String command = "";
unsigned long startTime = millis(); 
unsigned long timeInterval = 0; 
unsigned long LIMIT = 500;

void setup() {

  /* Todas las trazas se encuentran comentadas */
  
  //Serial.begin(9600);
  //while (!Serial);

  if (!APDS.begin()) {
    //Serial.println("Error initializing APDS9960 sensor!");
     while (1);
  }

   APDS.setGestureSensitivity(75); // [1..100]
  
  // begin ble initialization
  if (!BLE.begin()) {
    //Serial.println("starting BLE failed!");
    while (1);
  }

  //Serial.println("BLE Central - gesture control");

  pinMode(R_LED_PIN, OUTPUT);
  pinMode(G_LED_PIN, OUTPUT);
  pinMode(B_LED_PIN, OUTPUT);

  setColor("BLACK");
  

}

void loop() {
  
     connectToPeripheral();
}


void connectToPeripheral(){

  BLEDevice peripheral;

  do
  {
    // start scanning for peripherals
    BLE.scanForUuid(deviceServiceUuid);
    peripheral = BLE.available();
    
  } while (!peripheral);

  
  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    //Serial.print("Found  ");
    //Serial.print(peripheral.address());
    //Serial.print(" '");
    //Serial.print(peripheral.localName());
    //Serial.print("' ");
    //Serial.print(peripheral.advertisedServiceUuid());
    //Serial.println();
  
    // stop scanning
    BLE.stopScan();
  
    controlPeripheral(peripheral);
   
  }
  
}

void controlPeripheral(BLEDevice peripheral) {

  
  // connect to the peripheral
  //Serial.println("Connecting ...");

  if (peripheral.connect()) {
    //Serial.println("Connected");
  } else {
    //Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  //Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    //Serial.println("Attributes discovered");
  } else {
    //Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  BLECharacteristic gestureCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
    
  if (!gestureCharacteristic) {
    //Serial.println("Peripheral does not have gesture characteristic!");
    peripheral.disconnect();
    return;
  } else if (!gestureCharacteristic.canWrite()) {
    //Serial.println("Peripheral does not have a writable gesture characteristic!");
    peripheral.disconnect();
    return;
  }

  
  while (peripheral.connected()) {

    command = gestureDetectection();
    if (command != "")
    {
      int n = command.length(); 
      // declaring character array 
      char char_array[n + 1]; 
      // copying the contents of the string to char array 
      strcpy(char_array, command.c_str()); 
      gestureCharacteristic.writeValue((char*)char_array);
      delay(300);
      setColor("BLACK");
    }
  
  }

  //Serial.println("Peripheral disconnected!");

}
  
String gestureDetectection(){

  String _command;  

  if (APDS.gestureAvailable()) { // a gesture was detected
    
    gesture = APDS.readGesture();
    startTime = millis();
    
  }

  timeInterval = millis() - startTime;
    
  if (timeInterval > LIMIT)
  {

      switch (lastGesture) {
          case GESTURE_UP:
            //Serial.println("Detected UP gesture");
            setColor("GREEN");
            _command = "n\n";
            gesture = -1;
            break;
          case GESTURE_DOWN:
            //Serial.println("Detected DOWN gesture");
            setColor("GREEN");
            _command = "s\n";
            gesture = -1;
            break;
          case GESTURE_LEFT:
            //Serial.println("Detected LEFT gesture");
            _command = "w\n";
            setColor("GREEN");
            gesture = -1;
            break;
          case GESTURE_RIGHT:
            //Serial.println("Detected RIGHT gesture");
            _command = "e\n";
            setColor("GREEN");
            gesture = -1;
            break;
          default:
            ////Serial.println("NO gesture detected!");
            _command = "";
            gesture = -1;
            break;
        }
   
    
  }else{
  
     if ((gesture == GESTURE_DOWN) && (lastGesture == GESTURE_UP)) {
      
        //Serial.println("Detected GO gesture");
        setColor("BLUE");
        _command = "g\n";
        gesture = -1;
        
      }else if ((gesture == GESTURE_RIGHT) && (lastGesture == GESTURE_LEFT)) {
        
        //Serial.println("Detected RESET gesture");
        setColor("RED");
        _command = "r\n";
        gesture = -1;
        
      }else if ((gesture == GESTURE_UP) && (lastGesture == GESTURE_DOWN)) {
      
        //Serial.println("Detected GO gesture");
        setColor("BLUE");
        _command = "g\n";
        gesture = -1;
        
      }else if ((gesture == GESTURE_LEFT) && (lastGesture == GESTURE_RIGHT)) {
        
        //Serial.println("Detected RESET gesture");
        setColor("RED");
        _command = "r\n";
        gesture = -1;
      }
      
      
  }

  lastGesture = gesture;
  return _command;
  
    
}


void setColor(String color)
{

  if (color == "RED")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "GREEN")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, LOW);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "BLUE")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, LOW);
    
  }else if (color == "BLACK")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, HIGH);
  }        

}

Funcionamiento

Una vez cargado el Skecth en el Arduino Nano 33 BLE Sense ya estamos en condiciones de controlar el Escornabot por gestos. La lista gestos con los movimientos asociados son los siguientes (fijaros que la parte donde esta el conector USB del Arduino es la parte de “abajo” y el Arduino tendrá que estar posicionado tal y como se muestra en las siguientes imágenes):

Avanzar

Retroceder

Izquierda

Derecha

Ejecutar

NOTA: Movimiento de arriba – abajo o abajo – arriba en menos de medio segundo.

Resetear

NOTA: Movimiento de izquierda a derecha o de derecha a izquierda en menos de medio segundo

Consideraciones finales:

Como se puede apreciar los movimientos de “ejecutar” y “resetear” son movimientos rápidos de arriba – abajo (o viceversa) y de derecha a izquierda (o viceversa) que se tienen que hacer en menos de medio segundo. Por el contrario el resto de movimientos tendremos que esperar más de medio segundo en concatenarlos para que el programa no lo interprete como un movimiento de “ejecutar” o “reseteo”.

Por último comentar que la placa Arduino posee un LED RGB integrado que cuando se realicen los gestos de: AVANZAR, RETROCEDER, IZQUIERDA y DERECHA se iluminará de color verde. Si el gesto que se realiza es el de RESETEAR se iluminará en color rojo y cuando se realice el gesto de EJECUTAR se iluminará en color azul.