Category Archives: Conceptos

Neural Compute Stick Movidius

20 Nov , 2017,
Rebeca Rodriguez Tencio
,
No Comments

En los últimos años se escuchado el término de “machine learning” y los procesos que han existido para el análisis de los datos normalmente han sido ejecutados a través de la nube, sin embargo el Neural Compute Stick Movidius INTEL viene a ofrecer una alternativa adicional al proceso a través de un procesamiento local.

El Movidius Neural Compute Stick es el primer kit de aprendizaje basado en USB, que permite acelerar el entrenamiento de AI (Inteligencia Artificial), con una capacidad de procesamiento de una red neuronal.

El dispositivo utiliza una VPU (Vision Processing Unit) Myriad 2 personalizada que ofrece una potencia de 100 GFLOPs con un consumo de 1 vatio y 4GB de memoria LPDDR3, sin embargo una de las características que más me llamo la atención es el “multi-stick”, lo que permite utilizar varias unidades del Movidius Neural Compute Stick para que trabajen en forma conjunta (Si se conectaran 4 Movidius, tendríamos 400 GFLOPS de potencia, lo que es increíble para un USB que tiene un precio $79).

Caracteristicas relevantes:

  • Compilar: Movidius convierte automáticamente una red neuronal convolucional (CNN) basada en Caffe y ya entrenada en una red neuronal integrada y optimizada para operar directamente en la VPU Myriad 2 de Movidius.
  • Ajustar: Permite realizar ajustes efectivos para obtener un rendimiento óptimo con datos reales y con un consumo ultra bajo. Los scripts de validación permiten comparar la precisión de los modelos optimizados en el dispositivo con la de los modelos originales.
  • Acelerar: El Movidius Neural Compute es el único capaz de actuar como un acelerador periférico de redes neuronales añadiendo la capacidad de aprendizaje profundo a las actuales plataformas informáticas para mejorar el rendimiento y la eficiencia.

Conceptos relacionados:

  • VPU (Vision Processing Unit): Es un tipo de acelerador diseñado para procesamiento de argumentos gráficos, como redes neuronales profundas, estimación de pose, detección de profundidad 3D, entre otros.
  • CPU Risc (Reduced Instruction Set Computer): No se trata de usar pocas instrucciones, si no de simplificar la carga de datos de memoria sin realizar operaciones sobre ellos.

NOTA: La siguiente imagen es con fines ilustrativos, únicamente para conocer el Neural Compute Stick por dentro, no intente esto en casa!

Inteligencia Artificial o Aprendizaje de Máquinas

12 Nov , 2017,
Jose Nunez
, , , , ,
No Comments

Flor IRIS

Haciendo un resumen exagerado, podemos decir que la Inteligencia Artificial y/o el Aprendizaje de Máquinas son la forma en que hemos logrado que los sistemas artificiales (máquinas) logren aprender más o menos igual como nosotros los seres humanos lo hacemos: mediante el análisis de un conjunto de observaciones (o muestras) de forma que una vez realizado el aprendizaje, el sistema pueda realizar inferencias ya sea prediciendo un resultado basado en un conjunto de datos.

Las tareas de aprendizaje pueden ser de dos tipos: aprendizaje supervisado (cuando se tiene disponible el resultado durante el aprendizaje) y aprendizaje no-supervisado cuando no se tiene un resultado disponible.

A su vez, las tareas de parendizaje pueden incluir problemas de:

  1. Clasificación: cuando se necesita clasificar un conjunto de características en clases pre-definidas como valores discretos; por ejemplo determinar qué clase de planta está representada en un conjunto de datos.
  2. Regresión: cuando se necesita establecer un valor continuo como resultado de analizar un conjunto de características. Por ejemplo establecer el precio de un producto.
  3. Agrupamiento: cuando se necesita encontrar características comunes entre si que denoten grupos de datos. Por ejemplo la identificación de patrones en una imagen.

El proceso de aprendizaje de máquinas en general se puede definir en los siguientes pasos:

  1. Determinación del Problema: Identificar el objetivo, qué es lo que se quiere lograr (clasificación, regresión o agrupamiento) e identificar las características o atributos de datos relevantes (“features”); así como el tipo de proceso (supervisado o no-supervisado)
  2. Escogencia del algoritmo de aprendizaje: Existen diversos algoritmos para tomar una serie de datos y generar modelos de aprendizaje de máquinas. Estos algoritmos difieren uno de otro en su aplicabilidad para diferentes tipos de problemas. En este enlace se presenta un tutorial bastante sencillo sobre diferentes algoritmos y su evaluación para un problema determinado.
  3. Entrenamiento de un Modelo: Consiste en ejecutar el o los algoritmos elegidos usando el 80% de los datos conocidos (en el caso de aprendizaje supervisado) para generar un modelo que pueda hacer futuras inferencias con datos nuevos. Esto da como resultado un modelo entrenado.
  4. Validación del modelo entrenado: Consiste en ejecutar el algoritmo sobre el modelo entrenado usando el 20% restante y comparando los resultados con los valores conocidos para ese 20% de datos.
  5. Uso del modelo y refuerzo: Una vez alcanzada una precision deseada en los resultados (tal vez > 90%) se puede ya utilizar el modelo entrenado y validado para realizar nuevas inferencias a partir de nuevos datos donde no se tenga un resultado predefinido.

Como una nota final podemos decir que los conceptos de Aprendizaje de Máquinas y de Inteligencia Artificial se pueden separar citando un artículo reciente en la revista Forbes:

Inteligencia artificial (IA) es el concepto amplio de máquinas que son capaces de realizar tareas en una forma que se pueda considerar “inteligente”

y,

Aprendizaje de máquinas es la aplicación de paradigmas de IA al rededor de la idea de que deberia ser posibe que simplemente demos datos a las máquinas y que estas puedan aprender por sí mismas.

Acá algunos conceptos más avanzados que hemos logrado aprender: http://costaricamakers.com/?p=1455

______________________________________________

Referencias

CSS – RESUMEN DE 1 MINUTO

13 Oct , 2017,
Rebeca Rodriguez Tencio
No Comments

CSS son las siglas de Cascading Style Sheets (Hojas de Estilo en Cascada), que fue desarrollada por W3C (World Wide Web Consortium).

Es un lenguaje que describe la presentación de los documentos en pantalla y sirve para separar los contenidos de los documentos escritos en HTML, incluyendo colores, fondos, márgenes, bordes, tipos de letra, entre otros; es decir que CSS permite a los desarrolladores controlar el estilo y formato de los documentos.

Este lenguaje se basa en distintas reglas que vamos a explicar en este tutorial y que forman la sintaxis de las hojas de estilo, cada regla consiste en un selector y una declaración (las declaraciones van entre corchetes y poseen un atributo y un valor separado por dos puntos). Con CSS se puede controlar el diseño de varias páginas web de una sola vez.

Por ejemplo:

<style type="text/css">
  hoja2 {
    color: green;
    background-color: #3dcada}
  </style>

La explicación de las hojas de estilo es muy sencilla y está compuesta por 3 partes o reglas, la primera línea nos indica que eso es una hoja de estilo y que está escrita en CSS (“text/css”), la segunda línea nos indica que se añadirá un estilo a “hoja2 “, la tercera establece el color del texto como verde y en la siguiente línea nos dice que el fondo de la hoja sera de color “#3dcada” que es como turquesa.

  1. El selector (en el ejemplo sería el “hoja2”), este lo que hace es indicar al navegador que elementos HTML van a estar afectados por la declaración.
  2. La propiedad (en el ejemplo sería el “color” y “background-color”) las cuales nos indican qué aspecto del diseño o selector se va cambiar.
  3. El valor (“green” y “#d8da3d”) asigna un valor a la propiedad que puede ser color, alineación,tipo de fuente, etc.

Para aprender más sobre CSS puedes ingresar al siguiente enlace:

  • https://www.w3schools.com/css/default.asp

Algunos conceptos interesantes de Machine Learning usando Anaconda / Python

23 Sep , 2017,
Jose Nunez
, ,
No Comments

Continuando con nuestras recientes publicaciones sobre “Machine Learning” (artículo anterior sobre fundamentos), en esta oportunidad compartimos algunas cosas que hemos aprendido siguiendo el tutorial “Machine Learning in Python Step by Step“.

Para poder entender este artículo recomendamos seguir el tutorial paso a paso… no se toma más de 30 minutos.

  1. Anaconda: Aprendimos que se puede configurar un ambiente relativamente completo para experimentación con Machine Learning y Python usando Anaconda.
  2. Dataset IRIS: Existe un “Hello World” para Machine Learning basado en un dataset llamado “IRIS” 3. Este consiste en un conjunto de datos que describe tres tipos de flores Iris (setosa, virginica y versicolor) por las dimensiones de su sépalo y pétalo; se puede usar para entrenar un modelo de aprendizaje de máquina para que este infiera el tipo de flor (clasificación) con base en la combinación de parámetros.
  3. Arreglos: Python provee mecanismos para expresar y manipular arreglos de forma sumamente robusta. Podemos resumirlos de la siguiente manera:
    • Básicamente [a:b,c:d] donde a:b representa un rango de filas y c:d representa otro rango de columnas.
    • array[:,0:4] retorna todas las filas de la matriz y las primeras 4 columnas a partir de la columna cero.
    • array[:,4] retorna todos los elementos (filas) de la quinta columna (índice 4)
      
      
  4. Entrenamiento y Validación: El entrenamiento y validación de modelos de aprendizaje de máquinas usualmente suele dividir los datos conocidos en 80% para aprendizaje o creación del modelo y 20% para validación del modelo generado. En este tutorial se usa la función model_selection.train_test_split(X,Y, test_size, random_state) de la libreria sklearn.
  5. SKLEARN LIB: Existen diversos algoritmos de clasificación en la librería sklearn:
    1. LogisticRegression
    2. LinearDiscriminationAnalysis
    3. KNeighborsClassifier
    4. DecisionTreeClassifier
    5. GaussianNB
    6. SVM/SVC
  6. Precisión de Los Algoritmos: Diferentes algoritmos presentan diferentes niveles de precisión dependiendo del problema a resolver. Estos se pueden evaluar usando funciones como model_selection.cross_val_score que da como resultado medidas estadísticas como la media y la desviación estandar. Esta validación se puede confirmar con gráficos de tipo box charts, scattered matrix e histogramas. Estos gráficos se generan en python usando librerías como matplotlib
  7. Aprender y Predecir: Una vez entrenado el modelo (con knn.fit()) se pueden generar predicciones (knn.predict())
  8. Matriz de Confusión: Las predicciones pueden ser validadas mediante mecanismos como confusion_matrix que provee una análisis simple de valores esperados y valores predichos de manera correcta y errónea.
    • La matriz de confusión tiene un eje (x) que representa los valores conocidos, y un eje (y) que representa los valores predichos.
      setosa     ==> [[ 7   0   0]
      versicolor ==>  [ 0  11   1]
      virginica  ==>  [ 0   2   9 ]]
                        se  ve  vi
    • Esto se interpreta así:
      • Se identificaron 7 setosas adecuadamente.
      • De las 12 versicolor se identificaron 11 correctamente y una como virginica
      • De las 11 virginicas se identificaron 9 correctamente y 2 como versicolor.

Referencias:

  1. Machine Learning Step by Step: https://machinelearningmastery.com/machine-learning-in-python-step-by-step/
  2. Confusion Matrix: http://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html
  3. IRIS: https://es.wikipedia.org/wiki/Iris_flor_conjunto_de_datos
  4. Iris Setosa Imagehttps://www.rhs.org.uk/Plants/9355/Iris-setosa/Details

 

Modelando Bases de Datos Relacionales con XAMPP y phpMyAdmin

12 Sep , 2017,
Jose Nunez
, , , , , , ,
No Comments

Alguna vez habrán enfrentado la necesidad de modelar una base de datos relacional.

El mundo de bases de datos relacionales (RDBM) está regido por unos cuantos titanes en el tema. Desde Oracle, pasando por MS SQL Server y sin olvidar MySQL (y su hermana MariaDB) y PostgreSQL.

Para efectos de este artículo nos enfocaremos en MySQL y su nueva ramificación MariaDB.

MySQL forma parte de una pila de tecnologías web sumamente influyentes a nivel mundial. Nos referimos al “Stack LAMPP” que se traduce en Linux, Apache, MySQL, PHP/PERL.

En este conjunto de tecnologías, LINUX figura como un principal contendor en el mercado de sistemas operativos para servidores web (si es que existe tal cosa); mientras que Apache se desempeña como uno de los principales programas de servidor web a nivel mundial. Luego tenemos MySQL que es el componente de base de datos y finalmente PHP como lenguaje de programación para la web del lado del servidor.

Existen varios sistemas de instalación de la pila LAMPP; en nuestro caso usaremos una publicada por la organización ApacheFriends.org denominadada XAMPP. La “X” viene por la compatibilidad con sistemas operativos fuera de LINUX. Así XAMPP permite la instalación de la pila AMPP tanto en LINUX como en MS Windows y OSx de Mac.

A su vez, XAMPP trae algunas utilidades adicionales de las cuales vale la pena mencionar el sistema phpMyAdmin. Una herramienta avanzada que permite el diseño y manipulación de bases de datos MySQL.

Una vez que hemos descargado e instalado XAMPP en nuestro computador, y que nos hemos asegurado de que los servicios para Apache y MySQL están funcionando adecuadamente, podemos acceder al poder de XAMPP la dirección del servidor local: http://localhost/

La siguiente imagen muestra nuestra pantalla principal de XAMPP por medio del navegador.

Nótese entonces que en el menú superior a la derecha tenemos la opción “phpMyAdmin”

Seguidamente se ilustra como luce nuestra versión de phpMyAdmin en este momento.

Al lado izquierdo tenemos las diferentes bases de datos que están ya definidas en el servidor; mientras que a mano derecha tenemos el área de trabajo con diversos puntos de funcionalidad para editar bases de datos y cualquier otro elemento de base de datos.


PASO 1 – CREACIÓN DE BASE DE DATOS

Comenzamos este ejercicio haciendo clic en el nodo “New” que se encuentra a mano izquierda sobre la lista de bases de datos.

Esto nos muestra un formulario para crear una nueva base de datos que básicamente pregunta por el nombre de la base de datos y por el conjunto de caracteres (collation) predeterminado para la base de datos. Este parámetro define qué tipo de simbología usará la base de datos para almacenar texto. Uno de los conjuntos de caracteres más efectivos para occidente es el utf8 con sus variantes por idioma. Para este ejemplo que soporta símbolos en español y otros idiomas usaremos utf8_bin.

Una vez creada la base de datos podemos comenzar con el proceso de modelado.

Para esto utilizaremos el siguiente enunciado:

Se requiere diseñar una base de datos relacional para manejo de inventarios. El inventario contabiliza las cantidades disponibles de productos de diversos tipos; tomados del catálogo de productos que vende la organización. Para efectos de inventario se tienen dos tipos de producto: (1) los productos “serializables” que son productos que se pueden identificar de manera única (por número de serie asignado por el fabricante) y (2) los productos “a granel” que no se pueden identificar de manera unica sino por cantidades, por ejemplo 1 Kg de tornillos, o 300 destornilladores. El inventario se maneja en diversas bodegas y a su vez se contabilizan las entradas y salidas de inventario para y desde cada bodega.

Con este enunciado vamos a dar un siguiente paso para la identificación de los elementos fundamentales del modelo conceptual, es decir las entidades y sus relaciones.


PASO 2 – Identificación de Elementos

Una técnica que proponemos para esto es la de marcar el enunciado con un color (azul) aquellas partes que describen una entidad y con otro color (verde) aquellas partes que describen una relación. Así el enunciado queda de la siguiente manera:

Se requiere diseñar una base de datos relacional para manejo de inventarios. El inventario contabiliza las cantidades disponibles de productos de diversos tipos; tomados del catálogo de productos que vende la organización. Para efectos de inventario se tienen dos tipos de producto: (1) los productos “serializables” que son productos que se pueden identificar de manera única (por número de serie asignado por el fabricante) y (2) los productos “a granel” que no se pueden identificar de manera única sino por cantidades de alguna unidad de medida; por ejemplo 1 Kg de tornillos, o 300 destornilladores. El inventario se maneja en diversas bodegas y a su vez se contabilizan diferentes movimientos como entradas y salidas de inventario para y desde cada bodega.

De esta forma hemos identificado las siguientes entidades:

  1. Catálogo de Producto
  2. Tipo de Producto
  3. Unidad de Medida
  4. Bodega
  5. Movimiento
  6. Tipo de Movimiento
  7. Fabricante

También se identifican algunas relaciones:

  1. Catálogo de Producto >==> Tipo de Producto
  2. Catálogo de Producto >==> Fabricante
  3. Catálogo de Producto >==> Unidad de Medida
  4. Movimiento >==> Tipo de Movimiento (entrada/salida)
  5. Movimiento >==< Bodega

Finalmente algunos atributos relevantes son:

  1. Número de serie
  2. Cantidad
  3. Nombre del Producto
  4. Nombre de Bodega
  5. Descripción de Unidad de Medida
  6. Nombre de Tipo de Movimiento

PASO 3 – Modelo Conceptual

Una vez identificados los elementos básicos del modelo de datos, podemos realizar un diagrama de Entidad-Relación que nos permita comunicar cuales son las entidades que componen el modelo y como se relacionan entre si.

Aunque basta con Power Point o algun otro software que nos permita generar cajitas y conectarlas con líneas; en nuestro caso usaremos el poder de phpMyAdmin.

Para esto tomamos cada entidad y definimos las tablas respectivas con los valores más fundamentales que podemos pensar para cada entidad:

  1. Tipo de Producto (Código de Tipo de Producto, Descripción)
  2. Unidad de Medida (Código de Unidad de Medida, Descripción)
  3. Fabricante (Código de Fabricante, Nombre)
  4. Catálogo de Producto (Código de Producto, Descripción, Código de Tipo de Producto, Código de Fabricante, Código de Unidad de Medida)
  5. Bodega (Código de Bodega, Descripción)
  6. Tipo de Movimiento (Código de Tipo de Movimiento, Descripción)
  7. Movimiento (ID de Movimiento, Código de Bodega, Código de Producto, Código de Tipo de Movimiento, Cantidad)

Nótese que hemos reorganizado las entidades de forma que primero se definen aquellas de las cuales hay dependencia en otras entidades. También debemos acotar que los nombres de las tablas (entidades) y sus atributos los realizamos usando una nomenclatura estándar, donde “cd” refiere a código, “dsc” refiere a descripción y “id” refiere a identificador numérico automático. Se usan ID’s en las entidades transaccionales, mientras que se usan códigos en las entidades tipificadoras.

La siguiente imagen muestra cómo se definen los campos básicos de una tabla (entidad). Para poder acceder a este formulario se hace clic en el nodo de base de datos “inventario” a la izquierda. En el área de trabajo hay una opción para iniciar la creación de una tabla (entidad) con una cantidad predeterminada de columnas (atributos)

 


PASO 4 – Modelado de las Relaciones

Una vez definidas las entidades (tablas) podemos visualizarlas usando el área de la izquierda, expandiendo los nodos correspondientes como se muestra en la siguiente imagen.

A su vez, si hacemos clic en la base de datos “inventario” podremos acceder a la función “Designer” la cual nos permitirá ir construyendo las relaciones entre las entidades (tablas) mientras que construimos el diagrama Entidad-Relación correspondiente.

La siguiente imagen muestra la función de diseño, desde donde se pueden crear relaciones entre las diferentes tablas.

Si hacemos clic en “Toggle small/big” en el menú de la izquierda podremos ver todos los atributos.

También, si hacemos clic en “Create relationship” podremos seleccionar – primero – un atributo de tipo “llave primaria” y – segundo – un atributo en otra tabla de tipo “referencia foránea” y así conformar las relaciones.

De esta forma podemos ver el diagrama con relaciones logrado como sigue, el cual no solamente expresa las relaciones en sí, sino también la cardinalidad entre las entidades.

Volviendo al diagrama reducido a solamente las entidades podemos ver que queda así; incluyendo indicadores de cardinalidad entre las entidades.

Estos diagramas son sumamente útiles para discutir y razonar sobre el modelo de datos. A su vez se puede utilizar phpMyAdmin para agregar o modificar atributos en la fase de modelado detallado de la base de datos.

RFID Usando RDM6300 y Arduino 101, TinyTILE o ARDUINO UNO

7 Ago , 2017,
Jose Nunez
, , , ,
No Comments

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

El RDM6300 es un módulo bastante sencillo de utilizar en el entorno de ARDUINO gracias a la librería RDM6300 que se puede descargar de acá.

El módulo tiene tres conectores:

P1 (6 contactos): que contiene los pines para realizar comunicaciones. En este experimento utilizaremos solamente el pin 1 ubicado en la esquina que lo conectaremos al pin 6 del Arduino 101. Básicamente este es el único pin que se conecta al microcontrolador.

P2 (2 contactos) que es donde se conecta la antena (bobina)

P3 (3 contactos) que es donde se alimenta el sistema y donde se pude agregar un LED (todavía no tengo claro para qué exactamente) el punto es que de este solo utilizaremos el pin 2 y 3 que son +5V  y GND respectivamente.

El programa que utilizamos simula la apertura de un sistema mediante un pulso a un LED verde conectado al pin 3 de Arduino, y un pulso para cerrar simulado por un LED amarillo conectado al pin 2 del Arduino.

Algo interesante es que el Arduino 101 opera a 3.3V pero es tolerante a señales de 5V lo cual lo hace particularmente versatil (esto también aplica para TinyTILE. Con Arduino UNO también funciona.

Conexiones:

ARDUINO
PIN  2 ==> amarillo ====> LED Amarillo ===> Resistencia 150 Ohm ==> GND
PIN  3 ==> verde =======> LED Verde ======> Resistencia 150 Ohm ==> GND
PIN  6 <== café <======== PIN 1 del conector P1 del RDM6300 (TX)
PIN 13 ==> naranja =====> LED Verde ======> Resistencia 150 Ohm ==> GND
5V ======> rojo ========> Puerto + del protoboard
GND =====> negro =======> Puerto - del protoboard

RDM6300
PIN 2 del conector P3 (Vin) <===== Puerto + del protoboard (operando a 5V)
PIN 3 del conector P3 (GND) <===== Puerto - del protoboard

PIN 1 del conector P1 (TX) ======> PIN 6 del ARDUINO

Conector P2 =====< ANTENA (bobina)

 

Programa de ejemplo:

La versión oficial de este sketch se puede encontrar acá: https://github.com/janunezc/robotics/tree/master/rfid/rfid_rdm630_open_then_close

(!) También, será necesario que los archivos de la librería RDM6300 estén disponibles para compilar el sketch. Se puede copiar ambos archivos (rdm630.h y rdm630.cpp) en la misma carpeta del sketch, o en la carpeta de librerías del ARDUINO IDE (c:\user\myusername\documents\Arduino\libraries\rdm630\***). Es importante asegurarse que la librería RDM630 solo está copiada una vez en las diferentes rutas.

/*
 * Programa escrito por Jose Nunez como ejemplo didáctico del uso del RDM6300 con un ARDUINO 101.
 * Use bajo su propio riesgo.
 * Ejemplo de dominio público.
 *
*/
#include "rdm630.h"

rdm630 rfid(6, 0);  //TX-pin of RDM630 connected to Arduino pin 6
int led_yellow_close = 2;
int led_green_open = 3;
int led_signal = 13;
long OPEN_PULSE_WIDTH = 800;
long CLOSE_PULSE_WIDTH = 1000;
long OPEN_STATE_DURATION = 5000; 
long CODE_READ_DELAY = 10000;
long codeReadDelayMaxMillis = millis();

void setup()
{
    Serial.begin(115200);  // start serial to PC
    pinMode(led_yellow_close, OUTPUT);
    pinMode(led_green_open, OUTPUT);
    pinMode(led_signal, OUTPUT);
    rfid.begin();
    ledSignal(5,500);
    setMessage("READY!");
}

void loop()
{
  unsigned long rfidTagCode = 0;  
  
  if(rfid.available()){
    setMessage("RFID Data is available! reading it...");
    rfidTagCode = readRFIDCode();
    setMessage("Data: " + String(rfidTagCode));
    if(millis() > codeReadDelayMaxMillis) {
      if(rfidTagCode == 7598635) {
        setMessage("OPEN");
        openPulse();//This involves a closePulse() call inside the openPulse() function.
        codeReadDelayMaxMillis = millis() + CODE_READ_DELAY;
      } else {
        setMessage("I DONT KNOW YOU!");
        closePulse();
      }
    } else {
      setMessage("SkippingNewReads");
      delay(500);
    }   
  }
}

void openPulse(){
  setMessage("SENDING OPEN PULSE");
  digitalWrite(led_green_open, HIGH);
  delay(OPEN_PULSE_WIDTH);
  digitalWrite(led_green_open,LOW);
  setMessage("OPEN PULSE DONE! Waiting for OPEN STATE DURATION...");
  delay(OPEN_STATE_DURATION);
  setMessage("OPEN STATE COMPLETE");
  closePulse();
}

void closePulse(){
  setMessage("SENDING CLOSE PULSE");
  digitalWrite(led_yellow_close, HIGH);
  delay(CLOSE_PULSE_WIDTH);
  digitalWrite(led_yellow_close, LOW);
  setMessage("CLOSE PULSE DONE!");
}

unsigned long readRFIDCode(){
  byte data[6];
  byte length;

  rfid.getData(data,length);
  Serial.println("Data valid");
  for(int i=0;i<length;i++){
      Serial.print(data[i],HEX);
      Serial.print(" ");
  }
  Serial.println();
  //concatenate the bytes in the data array to one long which can be 
  //rendered as a decimal number
  unsigned long result = 
    ((unsigned long int)data[1]<<24) + 
    ((unsigned long int)data[2]<<16) + 
    ((unsigned long int)data[3]<<8) + 
    data[4];              
  Serial.print("decimal CardID: ");
  Serial.println(result);
  return result;  
}

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

void addToMessage(String message){
  setMessage(message, false);
}

void setMessage(String message){
  setMessage(message, true);
}

void setMessage(String message, bool newLine){
  String timeStamp = String(millis());
  String finalMessage = timeStamp + " - " + message;
  if(newLine){
    Serial.println(finalMessage);
  } else {
    Serial.print(message);
  }
}

Acá un ejemplo de la ejecución:

Espero que les sea de utilidad.

HTML – Resumen de 1 minuto

4 Ago , 2017,
Jose Nunez
, , , , , ,
No Comments

HTML son las siglas de Hyper-text Markup Language (Lenguaje de hiper-texto marcado). Es un lenguaje informático para describir un documento; y es una de las columnas vertebrales de la Internet moderna. Prácticamente todo el contenido que consumimos de la web se nos presenta usando HTML.

El formato HTML se basa en la definición de etiquetas (que describen partes de un documento) y atributos (que describen a las etiquetas)

Así por ejemplo la etiqueta <p> describe un párrafo en un documento y la etiqueta <div> describe una división de un documento.

Algunas etiquetas tienen incio y fin; por ejemplo la etiqueta de párrafo abre con<p> y cierra con </p>; de esta forma el contenido dentro de las etiquetas de apertura y cierre corresponde al contenido del párrafo.

<p>Este es un párrafo</p>

Algunas otras etiquetas son auto-cerradas. Por ejemplo la etiqueta <img> que define una imagen no tiene etiqueta “</img>” de cerrado sino que se declara así:

<img src="fotografia.jpg" />

La parte final de la etiqueta “/>” describe el auto-cerrado.

A su vez, en el ejemplo de la etiqueta “<img>” vemos también un ejemplo de lo que se conoce como atributo. La construcción src="fotografía.jpg" es un atributo que describe esa etiqueta <img>. Dice que se debe cargar un archivo denominado fotografía.jpg.

Existen algunos atributos que no tienen valor. Por ejemplo el atributo disabled.

<input type="text" value="algo de texto" disabled/>

En el ejemplo anterior se produce una caja de texto con el contenido “algo de texto” que aparece deshabilitada (en gris y no se puede cambiar el contenido).

Read More…

Documento Activo: ANGULAR 4 y TypeScript

1 Ago , 2017,
Jose Nunez
No Comments

1. Descripción

En este documento comparto mis anotaciones sobre cómo funciona Angular 4 y otros conceptos relacionados.


2. ¿Qué es una Aplicación Angular?

Una aplicación Angular es un programa o sistema construido mediante el framework Angular.

  1. Se compone de uno o más Módulos
  2. Cada módulo consite de uno o más Componentes que contienen:
    1. Metadata: Información sobre un componente
    2. Plantilla: La vista del componente (HTML y CSS)
    3. Clase: El controlador del componente (TypeScript)

3. CLI de Angular

Utilitario para generar proyectos Angular así como componentes y elementos en dichos proyectos. Implementa las mejores prácticas.

Instalación del utilitario CLI de Angular
npm install @angular/cli -g

https://cli.angular.io

https://github.com/angular/angular-cli/wiki


4. Creando una app Angular con Angular CLI

Una de las funciones del CLI de Angular es la creación de nuevos proyectos.

Creación de un proyecto con Angular CLI
ng new myProject

cd myProject

//Para abrir el proyecto usando Visual Studio Code:
code .   

//Para abrir el proyecto usando ATOM:
atom .

5. Anatomía de un proyecto Angular

Acá se describen las diferentes carpetas en que está estructurado un proyecto Angular
  1. /e2e/                 Carpeta para pruebas de tipo end-to-end
  2. /node_modules/        Paquetes de dependencias instalados con npm
  3. /src/app/             Código fuente del app. Incluye módulos y componentes
  4. /src/assets/          Activos reutilizables
  5. /src/environments/    Definiciones de ambientes
  6. /src/index.html       HTML principal del app
  7. /src/main.ts          TypeScript principal del app
  8. /src/polyfills.ts     Definiciones para la compatibilidad entre navegadores
  9. /src/styles.css       Estilos Generales
  10. /src/test.ts          Pruebas generales
  11. /src/tsconfig.app.json Configuración de TypeScript
  12. /src/tsconfig.spec.json Configuración de Pruebas con TypeScript
  13. /src/typings.d.ts     Tipos de dato personalizados en TypeScript

6. Anatomía de un módulo

Seguidamente se presenta el código de un módulo con comentarios sobre qué significa cada elemento
/*IMPORTS: Acá se incluyen las librerísa necesarias para que el componente pueda operar*/
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

/*Decorador @NgModule define la metadata correspondiente al módulo*/
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent] //Se incluye el componente AppComponent como parte del módulo.
})

/*Clase Principaldel Módulo. En este caso está vacía.*/
export class AppModule { }

7. Anatomía de un Componente

Aquí se presenta el código de un componente con comentarios sobre qué significa cada elemento
/*IMPORTS: Acá se incluyen las librerísa necesarias para que el componente pueda operar*/
import { Component } from '@angular/core';

/*Decorador @Component define metadata como la etiqueta que se usará para acceder al componente (selector) o los URL's a la plantilla o a los estilos.*/
@Component({ 
  selector: 'app-root', //Define la forma en que se puede hacer uso del componente. En este caso, mediante la etiqueta <app-root></app-root>
  templateUrl: './app.component.html', //Define cual plantilla HTML usará el componente.
  styleUrls: ['./app.component.css'] //Define cual archivo de estilos definirá la apariencia del componente
})

/*Clase principal del componente. Se exporta para hacerla accesible por parte de Angular y las vistas relacionadas.*/
export class AppComponent {
  title = 'app works!';
  sayHello() {
    alert("Hello");
  }
}

8. Ejecutando una App

El siguiente comando de consola ejecuta el app y abre (-o) un navegador para verla.

ng serve -o


9. Creando un Componente

Expande para ver cómo se crea un componente

El siguiente ejemplo genera un componente nuevo llamado Hello:

ng generate component Hello

El siguiente ejemplo solamente indica lo que se va a generar, pero no genera nada:

ng generate component Hello --dry-run

 


10. Haciendo Pruebas

Angular integra en sus proyectos la forma de desarrollar pruebas unitarias de código usando una metodología que se llama “Behavior Driven Development” o desarrollo basado en el comportamiento. Los archivos de tipo “*.spec.ts” en el proyecto definen este tipo de pruebas.

Expande para ver el comando para ejecutar las pruebas

ng test


11. Compilando la Aplicación

Mediante el proceso de compilación el código TypeScript es consolidado en archivos minificados de tipo JavaScript. A su vez el código HTML y CSS es también minificado y consolidado para ocupar la menor cantidad de espacio posible. El código es compilado en la carpeta ./dist dentro del proyecto.

Expande para ver el comando compilar la aplicación

ng build --prod

12. Notas sobre Data Binding

El proceso de “data binding” permite enviar datos desde el programa de un componente ( .ts ) hacia una vista o plantilla ( .html )

Para esto se supone que basta con agregar la directiva [(ngModel)]="datos" al código HTML de la plantilla donde “datos” es la variable definida en el archivo .ts del componente respectivo.

Ejemplo

Archivo hello.component.html

<p>
 {{greeting.toUpperCase()}}
</p>
<input type="text" [(ngModel)]="greeting" (keyup)="log($event)"/>

Archivo hello.component.ts

import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-hello',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.css']
})
export class HelloComponent implements OnInit {
greeting: string;
constructor() {
this.greeting = "Hello Joselito!";
}

log(event){
console.log(event);
}

ngOnInit() {
}
}

 

Nota Correctiva (Expande para leer más)

(!) Es importante destacar que para que funcione la directiva ngModel se debe asegurar que el archivo app.module.ts contenga la referencia a FormsModule y que esta sea inyectada dentro del descriptor de metadatos @NgModule tal y como se ejemplifica seguidamente.

 


 

Algunas buenas prácticas para programar

26 Jul , 2017,
Jose Nunez
, , , , , , ,
No Comments

Lineamientos Generales para todos los lenguajes

  1. Los métodos y variables privadas comienzan con minúscula y se escriben en formato “camelCase”
  2. Los métodos, variables y propeidades públicas comienzan con mayúscula y se escriben igual en formato “Camel Case”
  3. Las constantes se escriben usando todas las letras en mayúscula y separándo palabras con “guión bajo”. Ej.: MI_CONSTANTE
  4. Haga declaración explícita de las variables locales
  5. Evite el uso de variables globales
  6. Las declaraciones de variables se agrupan al principio de clases y métodos. En ocasiones es adecuado declarar una variable pública justo antes de la declaración de un método que use esa variable de manera exclusiva o prioritaria.
  7. Está bien declarar variables que se usan exclusivamente en bucles iterativos (for-loops) en la declaración de dichos bucles.
  8. Inicialice las variables que declara, y escriba su lógica de manera que pueda procesar valores “undefined/falsy/null”
  9. Evite el uso de raya baja o guiones en los nombres de métodos o variables. En casos excepcionales se puede usar raya baja para describir alguna situacion consistente a lo largo del proyecto. Por ejemplo es mejor escribir  var blueColor que  var blue_Color. Pero podría ser adecuado escribir  function testControllerInit_SuccessCase()
  10. Como convención de nombramiento, use verbos para los métodos y sustativos para las propiedades.
  11. Evite el uso de valores literales “hard0-coded” en diversos lugares. Hágalos reusables en constantes.
  12. Los operadores lucen mejor con espacios al rededor. Por ejemplo es mejor escribir  var x = 10 + 10;  que  var x=10+10;
  13. La indentación es primordial. Muchos editores de código traen herramientas para indentar adecuadamente el código.
    Good tools for code auto-format are Netbeans, Atom and Visual Studio Code.
  14. Utilice las herramientas de análisis estático de códigopara encontrar variables no utilizadas, métodos demasiado largos y errores de sintaxis. NetBeans provee herramientas muy útiles para lenguajes como JS, CSS, PHP, TypeScript, C++ y HTML.
  15. Mantenga las líneas de código de una longitud menor a 80 caracteres.
  16. Mantenga los métodos de una longitud menor a 30 lines
  17. Mantenga las anidaciones a menos de 3 niveles
  18. No deje código comentado. Escriba comentarios para encontrar código legado en su lugar.
  19. Evite comentarios innecesarios y reiterativos como /*This function creates a user*/ para una función llamada “CreateUser(args)”
  20. Aproveche las facilidades de los ambientes de desarrollo para generar documentación a partir de comentarios estructurados. Especialmente para los Web API’s y las API’s en general.

JavaScript

  1. Nunca declare objetos de tipo Number, String or Boolean
  2. No use “new Object()“. Use var x = {} en su lugar.
  3. Recuerde que JS tiene un mecanismo automático de conversión de tipos
  4. Use === en vez de  == siempre que se pueda.
    1. En ocasiones es apropiado usar  == cuando el tipo de los objetos no se ocnoce de manera predecible/estable. Sin embargo la situación de manejar objetos de tipo desconocido o impredecible debe ser evitada también.
  5. Ponga atención a los parámetros que adoptan una “definición” undefined cuando no se proveen en una llamada a una función y actúe consecuentemente.
  6. Termine los bloques “switch” con “defaults
  7. Nunca use eval()
  8. Use nombres de variable significativos comocuentaor valorPrevio en lugar de  c or vp
  9. Es apropiado utilizar funciones anónimas cuando estas serán llamadas solamente desde un mismo lugar. En caso contrario es necesario extraer las funciones con un nombre apropiado en el entorno (scope) correcto.
  10. No deje sentencias de tipo “debugging” tales como console.log en el código del ambiente productivo.
  11. En JavaScript, iniciar una declaración de bloque con una llave “{” en la misma línea de la declaración es correcto:var constants = {
       COLOR_RED: "#FF0000",
       COLOR_GREEN: "#00FF00",
       COLOR_BLUE: "#0000FF"
    };
  12. Referencias:
    1. https://www.w3schools.com/js/js_best_practices.asp
    2. https://www.w3schools.com/js/js_mistakes.asp
    3. https://www.w3schools.com/js/js_performance.asp

HTML

  1. Utilice etiquetas en minúscula de manera consistente. Use <div> en lugar de  <DIV>
  2. Asegúrese de cerrar todas las etiquetas HTML de manera apropiada. Por ejemplo,  <div> debe cerrar con una etiqueta  de cierre </div> mientras que <hr> luce mejor si se cierra de manera individual <hr />
  3. Utilice minúsculas para los nombres de atributo. Also, use camelCase format for ID’s of DOM elements. I.e.: <div id="myDiv">...</div>
  4. Utilice guiones para separar palabras en los nombres de las clases CSS: <div id="myDiv" class="jumbotron-alternative">...</div>
  5. Use comillas dobles para declarar los valores de atributos HTML: I.e.:<div id="myDiv" class="jumbotron-alternative">...</div>
  6. Use las etiquetas <table> apropiadamente para elementos tabulares no para generar estilos o layout.
  7. No agregue espacios entre los nombres de atributos, símbolo de igualdad y los valores de atributos.
    This is good<link rel="stylesheet" href="styles.css">
    This is bad: <link rel = "stylesheet" href = "styles.css">
  8. Utilice indentación apropiadamente, especialmente con elementos jerárquicos como listas, tablas, entradas de selección.
  9. Evite usar espacios en blanco, líneas en blanco o indentación innecesaria.
  10. Use comentarios de manera apropiada:
    Good for short comments:     <!-- This is a comment -->
    Good for longer comments:
    <!-- 
      This is a long comment example. This is a long comment example.
      This is a long comment example. This is a long comment example.
    -->
  11. Las definiciones de estilo van en un archivo separado, no en el código HTML. Nunca use estilos “en-línea”
  12. Coloque las referencias a archivos JavaScript al final del código HTML
  13. Valide su código HTML por medio de análisis estático. Netbeans tiene muy buenas herramientas de análisis estático para HTML.
  14. Utilice nombres con significado para variables y reglas de estilo/clases.
  15. Referencias:
    1. https://www.w3schools.com/html/html5_syntax.asp

CSS / SCSS

  1. Los estilos van en archivos separados del código HTML.
  2. Es apropiado escribir reglas cortas CSS en una sola linea.
  3. Las reglas más largas (que involucran multiples configuraciones) deben hacerse en modo multi-línea.
  4. Nótese que CSS suporta comentarios
  5. Referencias:
    1. https://code.tutsplus.com/tutorials/30-css-best-practices-for-beginners–net-6741

AngularJS Specific

  1. Organice las carpetas y archivos en una estructura basada en componentes.
  2. Remueva las dependencias no usadas en los controladores. Netbeans tiene buenas herramientas de análisis de código para detectar código no utilizado.
  3. Es apropiado usar funciones anónimas en controladores si estas serán llamadas desde un solo lugar. De lo contrario, es mejor agregar una función no-anónima y hacer las llamadas respectivas por medio de dicha función.
  4. Se recomienda usar notación de arreglo al instanciar controladores.
  5. Mantenga la lógica de presentación fuera de los controladores. Los controladores manipulan e modelo de datos y las vistas son quienes reaccionan a los cambios realizados en esos modelos. Los controladores no deben realizar llamadas de tipo document.getElementByID para encontrar elementos del DOM en una vista.
  6. Referencias:
    1. https://dzone.com/articles/angularjs-coding-best
    2. https://www.upwork.com/hiring/development/angularjs-best-practices/

C# and .Net

  1. Haga uso extensivo y prudente del registro de eventos de manera que se pueda habilitar solución expedita de problemas en ambientes productivos.
  2. WIP

SQL Server

  1. Tenga cuidado del acople y la cohesión al desarrollar Procedimientos Almacenados.
  2. Use Procedimientos Almacenados y/o vistas cuando esto mejore de manera significativa los procesos de acceso a datos. Especialmente consultas de múltiples tablas.
  3. WIP

TypeScript

  1. Una buena referencia a TypeScript: https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines

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;
}