Category Archives: Proyecto Grande (2-4 semanas)

Posibilidades con el pequeño Wheely

12 Ago , 2017,
Jose Nunez
No Comments

¿Recuerdan nuestro pequeño Robot de dos llantas?

Bien, le agregamos unos cuantos dispositivos más y veremos si lo podemos hacer un poco más listo.

  • Batería de Lítio-Polímero de 3.7V recargable. Para no gastar en baterías AA.
  • Regulador de 3V a 5V que nos permite usar la batería LIPO para alimentar una gama interesante de controladores y los motores a 5V.
  • Sparkfun Thing 8266: Este controlador nos da capacidades WIFI y además un mecanismo para cargar la batería LIPO usando un cable micro-USB convencional.
  • Arduino 101: Microcontrolador de gran capacidad que además trae acelerómetro y giroscópio en 6 ejes y comunicación Bluetooth BLE. Lo alimentamos a 5V, pero operan sus señales a 3.3V.
  • Sensor de proximidad para detectar obstáculos o mapear áreas que opera a 5V.
  • El ya conocido controlador S4A-EDU con puente H desconectabe. Nos permite controlar los motores en diferentes direcciones e incluye comunicación tx/rx que podemos usar para conectarlo a los otros micro-controladores mencioandos.

De momento no he programado mayor cosa con todo esto, salvo por este ejemplo; pero esperamos hacer algunas cosas interesantes con todas estas capacidades.

Usando la memoria FLASH del Intel Curie

24 Jul , 2017,
Jose Nunez
, ,
No Comments

Hola a todos.

Este es un tópico un poco más detallado.

El siguiente Sketch que pueden descargar de mi Github permite registrar datos en la memoria FLASH (persistente) del Intel Curie, buscarlos y eliminarlos.

Espero que el código sea bastante legible para todos.

Esta es una versión corregida del código original. Resulta que la librería SerialFlash no permite reutilizar porciones de memoria, así que después de varias operaciones creando y removiendo el archivo la memoria se acaba y se hace necesario borrar toda la flash (proceso que toma 1 segundo). Puede observarse en la línea 373 cómo se realiza este borrado cuando la creación “rápida” del archivo falla.

Acá implemento varias técnicas de “debugging” tales como parpadeos informativos, mensajes de consola estructurados y “pruebas unitarias” así como funciones de alcance y tamaño limitado.

Acá se resuelven  varios problemas, incluyendo aquello de ¿Cómo convierto un String a un const char*?  Bueno, no necesariamente una conversión, sino una implementación que es soportada por la función que estaba tratando de llamar. (ver linea 319)

En resumen, acá implemento funciones para crear un archivo en la memoria flash, con un formato predefinido. Leer el contenido de ese archivo y borrar el archivo.

Luego tambien implemento funciones para buscar contenido en el archivo y para eliminar contenido del archivo.

A su vez hay funciones para verificar el formato del archivo y realizar algunas pruebas unitarias.

El caso de uso es el almacenamiento de datos pequeños (códigos de acceso por ejemplo) que se puedan agregar, buscar o eliminar.

Capas:

  1. Acceso a Datos: Manejo de la memoria flash con archivos (crear archivo, guardar contenido, obtener contenido, eliminar archivo)
  2. Negocio: Agregar llave, eliminar llave, buscar llave.
  3. Desarrollo: diferentes pruebas y verificaciones.

Son una veintena de funciones pequeñas. Cada una hace lo suyo de manera muy explícita. Espero que les sea de utilidad.


// Escrito por José Núñez como un ejemplo didáctico para el dominio público.
// Utiliza la librería SerialFlash de Paul Stoffregen.
// Dicha librería se puede descargar de https://github.com/PaulStoffregen/SerialFlash

//Abstracción de la librería SerialFlash para el controlador Intel Curie
#include 
#include 

#define FSIZE 256 //Tamaño predefinido del archivo
#define ledPin 13 //PIN para un LED 
#define buttonPin 10 //PIN para leer un botón. Debe estar "pull-up" y el botón realiza una conexión a tierra.

const char *filename = "rfidble.txt"; //Nombre del archivo
#define CONTENT_SIZE 153 //Tamaño predefinido del contenido del archivo para este ejemplo.

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
  
  parpadear (5, 1000);

  debugMessage("Setup() BEGIN!");

  parpadear (3, 500);
  
  // Inicializar operación de la librería SerialFlash
  if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) {
    debugMessage("Setup() Unable to access SPI Flash chip");
  }

  debugMessage("Setup() DONE!");
}
/*------*/
/* LOOP */
/*------*/
bool executedOnce = false;
bool addKeyMode = false;
void loop() {
  if (!executedOnce) {
    debugMessage("loop() executing... reading file...");
    String fileContent = readFile();

    debugMessage("loop() file read: |" + fileContent + "| (" + String(fileContent.length()));

    debugMessage("loop() doing file verifications...");
    if (!verifyBegin(fileContent) || !verifyEnd(fileContent) || !verifySearch() || ! verifySize(fileContent)) {
      debugMessage("loop() Verifications Failed. Reformatting...");
      initFile();
      executedOnce = false;
    } else {
      debugMessage("loop() verifications ran OK");

      debugMessage("loop() testing key addition");
      test_addKey();

      debugMessage("loop() testing key removal");
      test_removeKey();
      executedOnce = true;
    }
  }
  
  processButtonCase();

}

void processButtonCase() {
  heartBeat();

  checkButton();

  if (addKeyMode) {
    addKey("1234");
    addKey("5678");
    addKey("91011");
    addKey("121314");
    addKey("151617");
    addKeyMode = false;
  }
}

unsigned long nextHeartBeat = 0;
void heartBeat() {
  if (millis() > nextHeartBeat) {
    debugMessage("Heartbeat!");
    parpadear (2, 200);
    nextHeartBeat = millis() + 2000;
  }
}

/******************************
   INTERACTION LAYER
 ******************************/

bool didResetMode = false;
void checkButton() {
  if (digitalRead(buttonPin) == LOW) {
    debugMessage("checkButton() Button is low!");
    long lowDetectionMillis = millis();

    while (digitalRead(buttonPin) == LOW) {
      long timeInLow = millis() - lowDetectionMillis;
      if (timeInLow > 3000) {
        debugMessage("checkButton() button was low for more than 3 seconds! Initializing File...");
        initFile();
        didResetMode = true;
        break;
      }
    }
    if (!didResetMode) {
      debugMessage("checkButton() Switching to addKey mode...");
      addKeyMode = true;
    }

    didResetMode = false;
  }
}

/*****************************
   DEBUG LAYER
 *****************************/
void test_addKey() {
  debugMessage("test_addKey() BEGIN!");
  String newKey = "2097876";
  debugMessage("test_addKey() Key to add: " + newKey);

  bool newKeyAlreadyFound = findKey(newKey);
  if (newKeyAlreadyFound) {
    debugMessage("test_addKey() WRONG: Key was not expected, but found. " + newKey);
  } else {
    debugMessage("test_addKey() CORRECT: New Key was not found, as expected. " + newKey);
  }

  debugMessage("test_addKey() Calling addKey()!");
  addKey(newKey);

  debugMessage("test_addKey() verifying addKey() results...");
  bool newKeyFound = findKey(newKey);
  if (newKeyFound) {
    debugMessage("test_addKey() CORRECT: New Key was found as expected. " + newKey);
  } else {
    debugMessage("test_addKey() WRONG: New Key was not found. " + newKey);
  }
}

void test_removeKey() {
  debugMessage("test_removeKey() BEGIN!");
  String keyToRemove = "2097876";
  debugMessage("test_removeKey() Key to remove: " + keyToRemove);

  bool keyToRemovePresent = findKey(keyToRemove);
  if (keyToRemovePresent) {
    debugMessage("test_removeKey() CORRECT: Key to remove is present as expected. " + keyToRemove);
  } else {
    debugMessage("test_removeKey() WRONG: Key to remove is not found!!!. " + keyToRemove);
  }

  debugMessage("test_removeKey() calling remove key...");
  removeKey(keyToRemove);

  if (findKey(keyToRemove)) {
    debugMessage("test_removeKey() WRONG: Key to remove is present. NOT EXPECTED!!!! " + keyToRemove);
  } else {
    debugMessage("test_removeKey() CORRECT: Key to remove not present as expected.");
  }

}
void parpadear(int times, int milliseconds) {
  for (int i = 0; i < times; i++) {
    digitalWrite(ledPin, HIGH);
    delay(milliseconds);
    digitalWrite(ledPin, LOW);
    delay(milliseconds);
  }
}

void debugMessageNoLF(String message) {
  Serial.print (message);
}

void debugMessage(String message) {
  long curMillis = millis();
  Serial.print (curMillis);
  Serial.print (": ");
  Serial.println (message);
}

bool verifyBegin(String fileContent) {
  debugMessage("verifyBegin() BEGIN!");
  if (fileContent.startsWith("FILE_BEGIN_OK")) {
    debugMessage("verifyBegin() CORRECT: File begin is OK");
    return true;
  } else {
    debugMessage("verifyBegin() WRONG: File begin is NOT AS EXPECTED");
    return false;
  }
}
bool verifyEnd(String fileContent) {
  debugMessage("verifyEnd() BEGIN!");
  if (fileContent.endsWith("FILE_END_OK")) {
    debugMessage("verifyEnd() Correct: End of file is as expected");
    return true;
  } else {
    debugMessage("verifyEnd() WRONG: End of file is NOT AS EXPECTED");
    return false;
  }
}

bool verifySize(String fileContent) {
  debugMessage("verifySize() BEGIN!");
  if (fileContent.length() == CONTENT_SIZE) {
    debugMessage("verifySize() CORRECT!");
    return true;
  } else {
    debugMessage("verifySize() WRONG!");
    return false;
  }
}
bool verifySearch() {
  debugMessage("verifySearch() BEGIN!");
  bool k1found = findKey("7598635");
  bool k2found = findKey("notfoundhere");

  if (k1found && ! k2found) {
    debugMessage("verifySearch() SEARCH IS OK!");
    return true;
  } else {
    debugMessage("verifySearch() ERROR!!!! SEARCH IS WRONG!!");
    return false;
  }
}


/******************************************
   BUSINESS LAYER
 ******************************************/

bool findKey(String key) {
  debugMessage("findKey(): " + key);
  String formattedKey = formatKey(key);
  String fileContent = readFile();
  int pos = fileContent.indexOf(formattedKey);
  if (pos > 0) {
    debugMessage("findKey() FOUND: " + key + " at " + String(pos));
    return true;
  } else {
    debugMessage("findKey() Not found: " + key);
    return false;
  }
}

void addKey (String key) {
  debugMessage("AddKey() BEGIN!");
  String fileContent = readFile();
  if (findKey(key) == true) {
    debugMessage("AddKey() Key already stored!");
    return; //Already there
  } else {
    int pos = fileContent.indexOf(",----------");
    String beginning = fileContent.substring(0, pos);
    String thisKey = formatKey(key);
    String restOfFile = fileContent.substring(pos + 11);
    String newFileContent = beginning + thisKey + restOfFile;
    debugMessage("OLD FILE:" + fileContent);
    debugMessage("NEW FILE:" + newFileContent);
    saveFile(newFileContent);
  }
}

String formatKey(String key) {

  int dashesRequired = 10 - key.length();
  String result = "";
  for (int i = 0; i < dashesRequired; i++) {
    result = result + "-";
  }
  return "," + key + result;
}
void removeKey(String key) {
  debugMessage("removeKey() BEGIN!");
  String fileContent = readFile();
  if (findKey(key) == false) {
    debugMessage("removeKey() key not present!");
    return; //Already not there
  } else {
    String formattedKey = formatKey(key);
    debugMessage("removeKey() Formatted Key: |" + formattedKey + "|");
    int pos = fileContent.indexOf(formattedKey);
    debugMessage("removeKey() Index of Formatted key: " + String(pos));
    if (pos > 0) {
      String beginning = fileContent.substring(0, pos);
      String thisKey = formatKey("");
      String restOfFile = fileContent.substring(pos + 11);
      String newFileContent = beginning + thisKey + restOfFile;
      debugMessage("removeKey() OLD FILE:" + fileContent);
      debugMessage("removeKey() NEW FILE:" + newFileContent);
      saveFile(newFileContent);
    } else {
      debugMessage("removeKey() Formatted Key not found! " + formattedKey);
    }
  }

}

/**
   This function initializes file with raw content.
*/
void initFile() {
  debugMessage("initFile() BEGIN!");

  String fileContent = "FILE_BEGIN_OK:123455,7598635---,----------,----------,----------,----------,----------,----------,----------,----------,----------,----------,FILE_END_OK";
  saveFile(fileContent);

  debugMessage("initFile() DONE!");
}


/********************
   DATA ACCESS LAYER
 ********************/

/**
   This function reads file content into an string. Implements full debugging mechanism
*/
String readFile() {
  debugMessage("readFile(): Begin!");

  debugMessage("readFile() Opening file: " + String(filename));
  SerialFlashFile file = SerialFlash.open(filename);

  // Get the size and position
  uint32_t fsize = file.size();
  debugMessage("readFile() Size: " + String(fsize));

  uint32_t filePos = file.position();
  debugMessage("readFile() Position: " + String(filePos));

  debugMessage("readFile() Seeking...");
  file.seek(filePos);

  debugMessage("readFile() Reading buffer...");
  char mybuff[fsize];
  file.read(mybuff, fsize);
  String result = String(mybuff);
  debugMessage("readFile() Closing file...");
  file.close();

  debugMessage("readFile() Content:");
  debugMessageNoLF("|||");
  debugMessageNoLF(result);
  debugMessageNoLF("|||");
  debugMessage("Size:" + String(result.length()));

  debugMessage("readFile() DONE!");
  return result;
}

/**
   This function drops existing file and saves a new one with proposed content.
*/
void saveFile(String newFileContent) {
  debugMessage("saveFile() BEGIN!");

  removeFile();

  uint8_t flashBuffer[CONTENT_SIZE + 1];
  newFileContent.getBytes(flashBuffer, CONTENT_SIZE + 1);
  SerialFlashFile file;


  debugMessage("saveFile() Creating file if not exist...");
  // Create the file if it doesn't exist
  while (!create_if_not_exists(filename)) {
    debugMessage("Memory filled up! Clearing Flash...");
    SerialFlash.eraseAll();

    while (SerialFlash.ready() == false) {
      // wait, 30 seconds to 2 minutes for most chips
    }
  }

  debugMessage("saveFile() Opening file for write!");
  // Open the file and write test data
  file = SerialFlash.open(filename);

  debugMessage("saveFile() Writing into file...!");
  file.write(flashBuffer, CONTENT_SIZE + 1 );
  debugMessage("saveFile() String \"" + String(newFileContent) + "\" written to file " + String(filename));

  debugMessage("saveFile() Closing file...");

  file.close();

  debugMessage("saveFile() DONE!");

}

/**
   This function drops the file.
*/
void removeFile() {
  debugMessage("removeFile() BEGIN!");

  if (SerialFlash.exists(filename)) {
    debugMessage("removeFile() Removing file...");
    SerialFlash.remove(filename);
  } else {
    debugMessage("removeFile() No file to remove..." + String(filename));
  }
  parpadear (3, 50);

  debugMessage("removeFile() DONE!");
}

bool create_if_not_exists (const char *filename) {
  if (!SerialFlash.exists(filename)) {
    debugMessage("Creating file " + String(filename));
    return SerialFlash.create(filename, FSIZE);
  }

  debugMessage("File " + String(filename) + " already exists");
  return true;
}

Comandos por BLE – (Parte 2 de 2) – El Transmisor (TX)

22 Jul , 2017,
Jose Nunez
, , , , , ,
No Comments

Como lo prometido es deuda, acá está la 2da entrega referente a controlar cosas con telefonos celulares mediante Bluetooth Low Energy (BLE) usando IONIC 3.

En esta segunda parte, implementaremos una App (Android y IOS) usando IONIC 3 que le envía comandos al micro-controlador mediante el sistema BLE del teléfono.

Para ver la Parte 1 haz clic acá

(!) Antes de seguir estas instrucciones asegúrese de entender las Condiciones de Uso de nuestro sitio.

Programando el App

En resumen:

  1. IONIC 3 es un sistema para crear aplicaciones móviles usando tecnologías híbridas (cordova, angularjs 4, typescript, html, css, etc.)
  2. Necesitaremos configurar IONIC3 en nuestro computador; esto incluye NodeJS, NVM, NPM y IONIC.
  3. También necesitaremos un muy buen editor de código para TypeScript. Puedo recomendarles Visual Studio Code o Atom.
  4. Descargamos el código del app de ejemplo de mi repositorio de Github y lo abrimos con el IDE de ARDUINO.git clone https://github.com/janunezc/robotics.gitEl código se ubica en ~/robotics/intel_curie/ble_command_app/
  5. En el celular podemos utilizar el sistema Ionic View (descargado del App Store o Google Play) y previsualizar el app 03B50F88Las siguientes imágenes muestran la operación del app

 

Seguidamente mostramos las partes más importantes del código original del programa con los comentarios detallados sobre su funcionamiento. Es realmente sencillo; podemos resumirlo en:

  1. El tab “SCAN” implementa un proceso básico de búsqueda de dispositivos Bluetooth. No vamos a detallar mucho en este tutorial sobre ese tab.
  2. El tab “LED” implementa la búsqueda de un dispositivo específico y una vez que lo encuentra permite interactuar con el LED para encenderlo y apagarlo.
    1. Los componentes (tabs) de la aplicación IONIC3 se dividen en 3 elementos principales: una vista o view (led.html), un controlador (led.ts) y un modelo que corresponde a los datos que va a manejar la aplicación y que se ubican en el objeto this y sus propiedades públicas.
    2. El archivo led.html es realmente simple. Aparte de los párrafos de texto expicativo y títulos, posee un botón que tiene algunas características importantes de AngularJS como lo son el atributo [disabled] ligado a una variable del modelo denominada “ejecutandoComando”. Cuando la variable tiene un valor verdadero (true) el atributo disabled se activa y el botón se deshabilita. A su vez tiene una directiva (click) que ejecuta la función “Command()” del controlador.
    3. Una parte importante del archivo led.html es el último párrafo en la línea 11. Este utiliza una directiva *ngFor para iterar sobre la variable “messages” y mostrar cada mensaje como un párrafo ( <p> ) individual. Esto nos permite mostrar mensajes de aclaración conforme se ejecutan los procesos en el controlador.
  3. Exploremos ahora el archivo led.ts. Se trata de un script escrito en TypeScript. Un pseudo-lenguaje basado en JavaScript que permite una orientación a objetos más elaborada.
    1. Lo primero que se realiza en led.ts es importar las librerías necesarias. Dos de las más notorias serán BLE y ApplicationRef. La primera permite comunicación BLE, y la segunda permite manipular el framework de Angular para refrescar la pantalla cuando sea necesario.
    2. La siguiente parte super importante es el constructor, el cual implementa una serie de inyecciones de dependencia que vale la pena mencionar.
    3. Nótese la forma en que se inyectan en el constructor tanto la librería BLE como la librería de Angular ApplicationRef. En el constructor también se inicializan variables importantes tales como el ID de servicio y el ID de Característica que se van a manipular.
    4. Las funciones más interesantes en este archivo son:
      1. findDevice() que busca un dispositivo particular y una vez que lo encuentra cambia el modo de comandos para encender o apagar un LED. Para esto usa la funcion de BLE nativo denominada ble.scan()
      2. txData() que utiliza ble.connect y ble.write para conectarse al dispositivo y hacerle llegar un mensaje.
      3. SetMessage() tambien merece ser mecionado como la forma que tenemos de agregar mensajes de control que nos permitan entender cómo se está ejectuando el programa.

Espero que este artículo les sea de utilidad junto con el anterior para aprender a enviar comandos a un dispositivo BLE basado en Intel Curie, mediante el teléfono celular.

Los dejo con estos enlaces de utilidad:

  1. Aprender TypeScript: https://www.typescriptlang.org/docs/home.html
  2. Aprender Ionic 3: https://ionicframework.com/
  3. Aprender Angular 4: https://www.youtube.com/watch?v=kFTmoLm9Jwg

Que estén bien!


led.html

<ion-header>
   <ion-navbar>
   <ion-title>LED</ion-title>
   <p>Acá podemos operar el LED que está implementado en TinyTILE</p>
   <button ion-button [disabled]="ejecutandoComando" (click)="Command()">{{ComandoTXT}}</button>
</ion-navbar>
</ion-header>
<ion-content padding>
   <p>Histórico de Ejecución: ({{myCount}} | {{test}})</p>
   <hr />
   <p *ngFor="let message of messages">{{message}}</p>
</ion-content>

led.ts

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { BLE } from '@ionic-native/ble';
import { ApplicationRef } from '@angular/core';

@Component({
  selector: 'page-led',
  templateUrl: 'led.html'
})

export class LEDPage {

  public messages = []; //Histórico de ejecución
  public ejecutandoComando = false;
  public BLE;
  public ComandoTXT = "";
  public Value = "";
  public TargetDevice;
  private appRef;

  /**
   * CONSTANTES que serán usadas a lo largo del controlador
   */
  public constants  = {
      DEVICE_NAME: "COMANDO LED",
      CMD_FIND_DEVICE:"FIND DEVICE",
      CMD_STOP_SCAN:"Escaneando... (Clic para parar)",
      CMD_TOGGLE_LED:"CAMBIAR LED",
      ON:"ON",
      OFF:"OFF"
    };

  /**
   * Constructor para el controlador. Todo comienza acá...
   */
  constructor(public navCtrl: NavController, private ble: BLE /*Librería BLE nativa*/, private applicationRef : ApplicationRef /*Librería ApplicationRef de Angular*/ ) {
    this.appRef = applicationRef; //Referencia a la aplicación ANGULAR
    this.SetMessage("Constructor: Iniciado!");
    this.BLE = ble; //Acceso a la librería BLE Nativa
    this.ComandoTXT = this.constants.CMD_FIND_DEVICE;
    this.Value = "ON"; //VALOR PARA ENVIAR
    this.TargetDevice = {}; //Dispositivo al cual conectarse. Inicialmente vacío... se llenará cuando se encuentre el dispositivo en el escaneo.
    this['service_id'] = "db938b80-f010-44b6-8aa9-1835adf9419a"; //Identificador del servicio BLE a conectarse
    this['characteristic_id'] = "9906064e-9bbe-4eba-b415-bbd223f7d3d9"; //Identificador de la característica BLE a conectarse
    this.SetMessage("Constructor: Finalizado!");
  }

  /**
   * This is triggered when the button is clicked.
   */
  public Command(){
    this.ejecutandoComando = true;
    this.SetMessage("Command() - Comando recibido!");
    this.SetMessage(this["ComandoTXT"]);
    this.SetMessage("Interpretando comando...");

    if(this['ComandoTXT']===this.constants.CMD_FIND_DEVICE) {
      this.SetMessage("Ejecutando Comando: FIND DEVICE");
      this['ComandoTXT'] = this.constants.CMD_STOP_SCAN;
      this.findDevice();

    } else if (this['ComandoTXT']===this.constants.CMD_STOP_SCAN) {
      this.SetMessage("Ejecutando Comando: STOP SCAN");
      clearInterval(this['intervalHandle']);
      this['ComandoTXT'] = this.constants.CMD_FIND_DEVICE;
      this.ejecutandoComando = false;

    } else if(this['ComandoTXT']=== this.constants.CMD_TOGGLE_LED){
      this.SetMessage("Ejecutando Comando: TOGGLE LED: " + this.Value);
      this.SetMessage("Acá es donde enviamos un valor al dispositivo BLE");
      this.txData();
    }
  }

  /**
   * This searches for DEVICE in the AIR using SCAN technique.
   */
  private findDevice(){
      this.SetMessage("FindDevice() - INICIO!");
      let  ble = this.BLE;

      this.SetMessage("FindDevice() - ENABLE!");
      ble.enable();

      this.SetMessage("FindDevice() - SET INTERVAL!");
      this['intervalHandle'] = setInterval(() => { // PROGRAMAR UN TIMER DE 2.1 SEGUNDOS
        this.SetMessage("INTERVAL: INICIO! LLAMANDO A BLE SCAN...");
        ble.scan([], 2 /*seconds (0) */).subscribe( data => { //REALIZAR SCAN BLE POR 2 SEGUNDOS
          this.SetMessage("BLE SCAN CALLBACK: " + data['id'] + ' | ' + data['name'] + ' | ' + data['rssi']);
          if(data['name']==this.constants.DEVICE_NAME){
            this.SetMessage(this.constants.DEVICE_NAME + " HA SIDO ENCONTRADO!!");
            clearInterval(this["intervalHandle"]);
            this.TargetDevice = data;
            this["ComandoTXT"] = this.constants.CMD_TOGGLE_LED;
            this.ejecutandoComando = false;
          }
          this.appRef.tick();
        });
      },2100);//FIN DE LA DEFINICIÓN DEL TIMER
      this.ejecutandoComando = false;
    }

  /**
   * Transmitir datos al dispositivo BLE
   */
  private txData(){
    this.SetMessage("txData(): INICIO! Llamando a BLE CONNECT...");

    let id = this.TargetDevice.id;
    this.SetMessage("ID DE DISPOSITIVO: " + id);

    this.ble.connect(id).subscribe(datos=>{
      this.SetMessage("BLE CONNECT CALLBACK: INICIO!. Llamando a BLE WRITE..." + this.Value);

      this.ble.write(this.TargetDevice.id, this['service_id'],this['characteristic_id'], this.StringToBytes(this.Value) ).then(()=>{
        this.SetMessage("BLE WRITE CALLBACK: INICIO! Cambiando valor... " + this.Value);
        if(this.Value==this.constants.ON){
          this.Value = this.constants.OFF;
        } else {
          this.Value = this.constants.ON;
        }
        this.SetMessage("Nuevo valor... " + this.Value);

        this.SetMessage("Llamando a BLE DISCONNECT...");
        this.ble.disconnect(id);
        this.ejecutandoComando = false;
        this.appRef.tick();
      },(error)=>{
        this.SetMessage("BLE Write ERROR!");
        this.SetMessage(error);
        this.SetMessage("Llamando a BLE DISCONNECT...");
        this.ble.disconnect(id);
        this.ejecutandoComando = false;
        this.appRef.tick();
      });
    },error=>{
      this.SetMessage("BLE Connect ERROR!");
      this.SetMessage(error.message);
      this.ejecutandoComando = false;
      this.appRef.tick();
    });
  }

  /**
   * Agrega mensajes de ejecución al histórico
   */
  public SetMessage(message){
    var count = this['messages'].length;
    message = count + ':' + message;
    this['messages'].unshift(message);

    /*
      ESTA LINEA ES IMPORTANTE PARA REFRESCAR LA PANTALLA CUANDO SE GENERAN EVENTOS
      FUERA DEL CONTROL DE ANGULAR. POR EJEMPLO CUANDO SE LLAMA A UN CALLBACK EN
      UN PROCESO DE BLE SCAN.
    */
    this.appRef.tick();
  }

  // ASCII only. Convierte el valor a escribir a un arreglo de BYTES
  public StringToBytes(string) {
     let array = new Uint8Array(string.length);
     for (let i = 0, l = string.length; i < l; i++) {
         array[i] = string.charCodeAt(i);
      }
      return array.buffer;
  }

  // ASCII only
  public BytesToString(buffer) {
      return String.fromCharCode.apply(null, new Uint8Array(buffer));
  }
}