domingo, 10 de enero de 2016

Fase 4. Implementación de la Máquina de Estados en el controlador


En esta entrada se comenzará la implementación el controlador siguiendo el paradigma de Máquina de Estados introducido anteriormente. Esto permite que las situaciones en las que se encuentra el programa (estados), se encuentren bien definidas evitando así, situaciones no esperadas que puedan saltarse el correcto funcionamiento del mismo produciendo incongruencias en los resultados.

Como se mencionó en la entrada anterior, se hará uso de la librería FSM implementada por Igor Real. Será necesario importarla usando el Arduino IDE y hay que tener claro el funcionamiento constante que sigue:

1) Leer eventos.
2) Actualizar la máquina de estado.
3) Ejecutar el comportamiento del estado.
4) Actualización de estados.

Lo primero que se necesita conocer es el diagrama de estados que se quiere implementar, en nuestro caso tendríamos:




Una vez definido el diagrama, en este caso se ha comenzado modificando el fichero myStates.h . A través de cuatro zonas bien diferenciadas es posible indicar cual será el funcionamiento que tendrá el programa:

1) Declaración de funciones: en esta parte se indican las funciones que van a ser llamadas dentro de los estados, esto se abordará más adelante.

2) Declaración del nombre de los estados y los eventos: en esta parte, se asigna  un valor hexadecimal y a través de un mnemotécnico se establece el nombre del estado o el evento.

3) Estructuras descriptivas del diagrama de flujo: como se comentó en el primer punto, las funciones definidas van ligadas a un estado. Esto viene dado de la siguiente forma: {ESTADO, FUNCION}.

4) Transiciones entre estados: en esta última zona, se indican los cambios de estados y que evento es el que lo produce, la estructura que tiene es: {ESTADO_ACTUAL, EVENTO, ESTADO_SIGUIENTE}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#ifndef myStates_H
#define myStates_H


//Declaracion de las funciones
extern void abrir(void);
extern void cerrar(void);
extern void verPlazas(void);
extern void calibrar(void);
extern void inicio(void);

//Declaracion del nombre de ESTADOS y de EVENTOS
#define STATE1   0x01
#define STATE2   0x02
#define STATE3    0x03
#define STATE4    0x04
#define STATE5    0x05


#define PETICION0     0x21
#define PETICION1     0x23
#define PETICION2     0x25
#define TIEMPO        0x42

// Estructuras descriptivas de mi diagrama de flujo
const FSMClass::FSM_State_t FSM_State[] PROGMEM= {
// STATE,STATE_FUNC
{STATE1,inicio},
{STATE2,calibrar},
{STATE3,abrir},
{STATE4,cerrar},
{STATE5,verPlazas},
};

//Transiciones entre estados
const FSMClass::FSM_NextState_t FSM_NextState[] PROGMEM= {
// STATE,EVENT,NEXT_STATE
{STATE1,PETICION0,STATE2},
{STATE2,0,STATE1},
{STATE1,PETICION1,STATE3},
{STATE3,0,STATE4},
{STATE4,0,STATE1},
{STATE1,PETICION2,STATE5},
{STATE5,0,STATE1},
};


//Macros para el cálculo de los tamaños de las estructuras
#define nStateFcn  sizeof(FSM_State)/sizeof(FSMClass::FSM_State_t)
#define nStateMachine  sizeof(FSM_NextState)/sizeof(FSMClass::FSM_NextState_t)

#endif

Una vez terminado de editar el archivo myStates.h, se comienza a modificar el fichero del controlador. Como no podría ser de otra forma, se añaden  la librería FCM.h y el fichero myStates.h

En mi caso ha sido necesario modificar en el fichero FSM.cpp la línea #include "WProgram.h" y cambiarla por #include "Arduino.h".

En el setup se añade la siguiente línea:

1
  FSM.begin(FSM_NextState,nStateMachine,FSM_State,nStateFcn,STATE1);

La idea es que el loop quede de la siguiente forma, ofreciendo el control del programa únicamente a las transiciones que definimos:

1
2
3
4
5
void loop()
{
  ReadEvents();
  FSM.Update();
}

En las funciones que llamamos es importante añadir un detalle al final para el correcto funcionamiento, este detalle viene dado por a siguiente línea:
  

1
 FSM.AddEvent(0);

Uno de los pasos más importantes es la función ReadEvents() en la cual se define como van cambiando los eventos que producen los cambios de estado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void ReadEvents(void){
  myEvent=0;
  if(pos==0){
    myEvent=PETICION0;
  }else if(pos==1 && plazasOcupadas<2 ){
    myEvent=PETICION1;
  }else if(pos==2){
    myEvent=PETICION2;
  }
   FSM.AddEvent(myEvent);  
}


Para más información : https://github.com/iiotUHU/iiot








Fase 3.Fusión de sensores usando lógica fuzzy - Ejecución

En esta entrada se va a tratar la fusión de sensores. En este caso se ha optado por usar la lógica fuzzy, también llamada lógica difusa, ya que da bastantes buenos resultados, ya que se adapta más al mundo real, al no usar valores fijos (5 o 10), sino un rango de valores (5 a 10) y es capaz de adaptar expresiones humanas como "hace mucho calor" o "no hace frío", donde no existe una certeza de qué cantidad de temperatura tiene que hacer para que se pueda pensar una opción u la otra.

La clave de la lógica fuzzy es que trata de comprender los cuantificadores de cualidad para nuestras inferencias (mucho, poco, muy, etc.). También esta lógica hace uso de operaciones sobre conjuntos como la unión, la intersección, diferencia, negación, etc. para llevar a cabo el tratamiento de la información y llegar a un resultado.
En la lógica fuzzy existen dos conjuntos difusos básicos llamados antecedentes (conjunto difuso de entrada) y consecuentes (conjunto difuso de salida). Por otro lado, para cada elemento a tratar existe una función de pertenencia, que indica en qué medida el elemento forma parte de ese conjunto difuso.

En la imagen anterior se puede ver la temperatura, que contiene 3 etiquetas, correspondientes a los trapecios que se aprecian. Cada una de estas etiquetas tienen un rango de valores, donde en ciertos puntos la certeza es máxima (1, donde habría un grado de pertenencia máximo a la etiqueta de frío), es decir, se está seguro de que hace frío (cold) y otros donde empieza a subir la temperatura y según donde esté se puede considerar frío en cierto grado o comenzar a acercarse más a una temperatura más cálida.

Además de lo anteriormente mencionado, la lógica fuzzy hace uso de una serie de reglas heurísticas, del tipo SI (antecedentes) ENTONCES (consecuente). Un ejemplo sería:
  • Si hace mucho frío entonces subir mucho la calefacción.
  • Si hace poco frío entonces subir un poco la calefacción.
  • Si hace mucho calor entonces apagar la calefacción.
 Dependiendo del valor de entrada, éste tendrá un grado de pertenencia con cada una de las reglas y se tomará una decisión (consecuente) de acuerdo a ese grado. La decisión puede ser la regla con mayor grado, la suma aritmética de todos los grados con el mismo consecuente y eligiendo el de mayor valor y otra serie de técnicas.

Una vez explicado por encima el principio básico de la lógica fuzzy y para llevarla a cabo con Arduino se ha optado por usar la "librería Fuzzy para Arduino y sistemas embebidos", comentada en la entrada de Fusión de sensores. Esta librería es bastante tediosa de usar, pero, una vez conocida la estructura a usar, es bastante fácil de manejar.
A continuación se procede a explicar el uso de esta librería:

Lo primero que hay que hacer es descargar la librería y añadirla en Arduino haciendo uso de la inclusión de librerías de Arduino (simplemente se añade un zip con los ficheros descargados del GitHub del proyecto y en Arduino ir a Programa -> Incluir librería -> Añadir librería .zip). Una vez se añade la librería se incluye esta al programa eligiendo la nueva opción que aparecerá llamada eFLL dentro de la opción de Incluir librería anteriormente mencionada.

A continuación se generaría un objeto de la clase Fuzzy y se generarían las etiquetas correspondientes a cada uno de los conjuntos a definir. En nuestro caso como conjunto de entrada se tendría la luz con dos etiquetas correspondientes a que no existe luz o existe luz, el conjunto de entrada para la distancia con tres etiquetas que serían no existe coche, coche alto y coche bajo. Para el conjunto de salida simplemente tendría dos etiquetas correspondientes a si hay un coche o no.

En el siguiente extracto del programa se ve lo explicado anteriormente:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <Fuzzy.h>
#include <FuzzyComposition.h>
#include <FuzzyInput.h>
#include <FuzzyIO.h>
#include <FuzzyOutput.h>
#include <FuzzyRule.h>
#include <FuzzyRuleAntecedent.h>
#include <FuzzyRuleConsequent.h>
#include <FuzzySet.h>

// Paso 1 - Instanciando un objeto de la librería fuzzy
Fuzzy* fuzzy = new Fuzzy();

//Paso 2 - Crear etiquetas
//Etiquetas luz
FuzzySet* noExisteLuz = new FuzzySet(0, 0, 275, 425);
FuzzySet* existeLuz = new FuzzySet(275, 425, 800, 800);

//Etiquetas distancia
FuzzySet* cocheAlto = new FuzzySet(0, 0, 127.5, 142.5);
FuzzySet* cocheBajo = new FuzzySet(127.5, 142.5, 155, 165);
FuzzySet* noExisteDistancia = new FuzzySet(165, 170, 210, 210);

//Etiquetas salida
FuzzySet* noExisteCoche = new FuzzySet(0, 0, 0, 0);
FuzzySet* existeCoche = new FuzzySet(1, 1, 1, 1);

Una vez se crean las etiquetas hay que crear los conjuntos de entrada y salida usando esas etiquetas. También se generaría el conjunto de reglas que se usa, como por ejemplo, Si no hay luz y coche alto Entonces existe coche.
Todo ello se ha añadido a una función para hacer uso de esta en el setup. A continuación se ve lo descrito:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
void initFuzzy(){
  // Paso 3 - Creando conjunto de entrada de luz
  FuzzyInput* luz = new FuzzyInput(1);// Como parámetro necesita un ID
 luz->addFuzzySet(noExisteLuz); // Se añade la etiqueta
 luz->addFuzzySet(existeLuz);

 fuzzy->addFuzzyInput(luz);

 //Igual para la distancia
 FuzzyInput* distancia = new FuzzyInput(2);// Como parámetro necesita un ID
 distancia->addFuzzySet(noExisteDistancia); // Se añade la etiqueta
 distancia->addFuzzySet(cocheBajo);
 distancia->addFuzzySet(cocheAlto);
 fuzzy->addFuzzyInput(distancia); // Se añade el conjunto al objeto fuzzy
 
 // Paso 4 - Creando conjunto de salida
 FuzzyOutput* coche = new FuzzyOutput(1);// ID de parámetro
 coche->addFuzzySet(noExisteCoche); // Añadir etiqueta al conjunto de salida
 coche->addFuzzySet(existeCoche);
 
 fuzzy->addFuzzyOutput(coche); // Añadir el conjunto de salida al objeto fuzzy
 
 //Paso 5 - Conjunto de reglas

 //Consecuentes
 FuzzyRuleConsequent* thenExisteCoche = new FuzzyRuleConsequent(); //Creación de consecuente
 thenExisteCoche->addOutput(existeCoche);// Juntando consecuente con salida

 FuzzyRuleConsequent* thenNoExisteCoche = new FuzzyRuleConsequent();
 thenNoExisteCoche->addOutput(noExisteCoche);
 
 // Regla 1 - Si existe luz y no existe distancia, existe un coche
 FuzzyRuleAntecedent* siNoExisteLuzYnoExisteDistancia = new FuzzyRuleAntecedent(); //Creando antecedente
 siNoExisteLuzYnoExisteDistancia->joinWithAND(noExisteLuz, noExisteDistancia); //Añadiendo etiquetas al antecedente
 
 FuzzyRule* fuzzyRule01 = new FuzzyRule(1, siNoExisteLuzYnoExisteDistancia, thenExisteCoche); // Creando la regla
 fuzzy->addFuzzyRule(fuzzyRule01); // Añadiendo regla al conjunto de reglas
  
 // Regla 2 - Si existe luz y coche alto, existe un coche
 FuzzyRuleAntecedent* siNoExisteLuzYcocheAlto = new FuzzyRuleAntecedent();
 siNoExisteLuzYcocheAlto->joinWithAND(noExisteLuz, cocheAlto);
 
 FuzzyRule* fuzzyRule02 = new FuzzyRule(2, siNoExisteLuzYcocheAlto, thenExisteCoche);
 fuzzy->addFuzzyRule(fuzzyRule02);

 // Regla 3 - Si existe luz y coche bajo, existe un coche
 FuzzyRuleAntecedent* siNoExisteLuzYcocheBajo = new FuzzyRuleAntecedent();
 siNoExisteLuzYcocheBajo->joinWithAND(noExisteLuz, cocheBajo);
 
 FuzzyRule* fuzzyRule03 = new FuzzyRule(3, siNoExisteLuzYcocheBajo, thenExisteCoche);
 fuzzy->addFuzzyRule(fuzzyRule03);

 // Regla 4 - Si no existe luz y no existe distancia, no existe un coche
 FuzzyRuleAntecedent* siExisteLuzYnoExisteDistancia = new FuzzyRuleAntecedent();
 siExisteLuzYnoExisteDistancia->joinWithAND(existeLuz, noExisteDistancia);
 
 FuzzyRule* fuzzyRule04 = new FuzzyRule(4, siExisteLuzYnoExisteDistancia, thenNoExisteCoche);
 fuzzy->addFuzzyRule(fuzzyRule04);

 // Regla 5 - Si no existe luz y coche Alto, existe un coche
 FuzzyRuleAntecedent* siExisteLuzYcocheAlto = new FuzzyRuleAntecedent();
 siExisteLuzYcocheAlto->joinWithAND(existeLuz, cocheAlto);
 
 FuzzyRule* fuzzyRule05 = new FuzzyRule(5, siExisteLuzYcocheAlto, thenExisteCoche);
 fuzzy->addFuzzyRule(fuzzyRule05);

 // Regla 6 - Si no existe luz y coche bajo, no existe un coche
 FuzzyRuleAntecedent* siExisteLuzYcocheBajo = new FuzzyRuleAntecedent();
 siExisteLuzYcocheBajo->joinWithAND(existeLuz, cocheBajo);
 
 FuzzyRule* fuzzyRule06 = new FuzzyRule(6, siExisteLuzYcocheBajo, thenNoExisteCoche);
 fuzzy->addFuzzyRule(fuzzyRule06); 
}

Una vez se tiene creada la lógica fuzzy, para hacer uso de esta se usan los métodos que esta librería incluye. Básicamente se trata de darle un valor a cada uno de los conjuntos de entrada, usar la función de fuzzify, ver el grado de emparejamiento si se desea usando el método getPertinence de cada etiqueta y recoger la salida del conjunto de consecuente con deffuzify.
Hay que tener en cuenta que cada conjunto de entrada y salida tiene asociado un identificador para poder llamarlos después.
Todo lo descrito anteriormente se ha plasmado en un método, que recibe como parámetros de entrada la luz y la distancia y devuelve si no hay coche (0) o si lo hay (1) que son los valores correspondientes a las etiquetas del conjunto de salida. Este método es el que se usará en el loop


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int getFuzzyValue(float luz, float distancia){

  Serial.println("Datos que llegan: ");
  Serial.print("Luz: ");
  Serial.print(luz);
  Serial.print(" distancia ");
  Serial.println(distancia);
  
  //Paso 7 - Iniciar entradas y fuzzificar
  fuzzy->setInput(1, luz);
  fuzzy->setInput(2, distancia);
 
  fuzzy->fuzzify();

   //Ver pertenencia a cada etiqueta
  Serial.print("Luz: ");
  Serial.print("no existe luz: ");
  Serial.print(noExisteLuz->getPertinence());
  Serial.print(", existe luz ");
  Serial.println(existeLuz->getPertinence());
  
  Serial.print("Distancia: ");
  Serial.print("No existe distancia: ");
  Serial.print(noExisteDistancia->getPertinence());
  Serial.print(", Coche alto: ");
  Serial.print(cocheAlto->getPertinence());
  Serial.print(", cocheBajo");
  Serial.println(cocheBajo->getPertinence());

 //Paso 8 - Defuzzificar y coger valor salida
  int coche = fuzzy->defuzzify(1);
   
  Serial.print("Existe coche? ");
  Serial.println(coche);

  return coche;
}

Con estos dos métodos de creación y uso del Fuzzy se finalizaría la explicación de esta fase. Habría que mencionar que cada una de las etiquetas hay que darle un valor para generar un trapezoide como se ve con la temperatura. Estos valores se han extraído de la experimentación y se han puesto directamente para ahorrar tiempo, pero lo ideal sería que esos valores se automatizaran usando los datos de la calibración esta forma de generar las etiquetas usando los valores almacenados en la EEPROM se pueden ver en el repositorio, en el enlace siguiente:

Plaza arduino con etiquetas fuzzy generadas automáticamente

sábado, 9 de enero de 2016

Fase 2. Comunicación

En esta entrada se han incorporado diferentes funcionalidades, como la capacidad de almacenar los valores proporcionados por la calibración en la memoria EEPROM o la inclusión de un nuevo elemento en el sistema. Esto conlleva una serie de medidas necesarias como el uso de un protocolo de comunicación entre los distintos microcontroladores que forman el sistema permitiendo de esta forma, un intercambio de información garantizando la correcta transmisión de datos entre ellos.

Se comenzará con los cambios realizados para la plaza. En primer lugar se va a explicar el uso de la memoria EEPROM del Arduino. Se trata de una pequeña memoría no volátil, es decir, no se elimina la información al reiniciarse el dispositivo, que se puede utilizar para guardar una cantidad de datos limitada. El dispositivo que se utiliza en las plazas es un Arduino UNO que dispone de un total de almacenamiento de 1 KB. La librería usada es la que incluye en el IDE, EEPROM.h con la que se implementarán dos métodos, el saveEEPROM y el loadEEPROM los cuales se pueden ver en el archivo plazaV2.ino del repositorio.



Como se expuso anteriormente, se incluye otro dispositivo al sistema. Para comunicar ambos dispositivos se usará en un principio el protocolo I2C, se trata de una comunicación serie que principalmente se usa para establecer un diálogo entre microcontroladores y/o sus periféricos. Para establecer la comunicación se necesita darle una dirección a cada elemento que interfiere en la misma. Utiliza tres líneas, dos de ellas para comunicación donde una de ellas se utiliza para el reloj (SCL) y otra para los datos (SDA), la tercera línea sería tierra (GND).



El uso de I2C en Arduino es muy sencillo, para ello se necesita la librería Wire.h y a través de interrupciones se realiza la comunicación como se puede ver en el archivo plazaV2.ino.

Una vez terminado con la plaza, se comienza con la explicación del nuevo elemento del sistema. Este controlador será el encargado de coordinar los otros dispositivos y conseguir la correcta sincronización del sistema. Para ello se conectará a través del bus I2C como maestro, a través del shield LCDKeypad se selecciona que opción tiene que realizar, en este momento se dispone de "CALIBRAR", "ABRIR" y "VER PLAZAS".



En la opción "CALIBRAR" mandará una petición a la plaza y esta comenzará con el calibrado. Por otro lado "ABRIR" acciona el servomotor de la entrada, levantando la barrera y permitiendo el acceso de un vehículo. Por último "VER PLAZAS" indica el total de plazas libres que se encuentran en el garaje. Para más información o ver el código, se puede consultar el fichero iiotcontroller.ino
que se encuentra en el repositorio.

Repositorio: https://github.com/iiotUHU/iiot/