Microcontroladores e inteligencia artificial embebida

En los últimos años hay dos áreas que han avanzado rápidamente. La inteligencia artificial y todo lo que rodea los microcontraladores y sensores. Sin embargo los avances de una y otra parece separarlas. La I.A. se mueve hacia grandes redes neuronales que requieren grandes máquinas con mucha potencia y memoria. Si bien los microcontroladores están bajando de precio y aumentando sus capacidades quedan muy atrás de lo que necesitan las I.A. actuales.

Ambos están condenados a entenderse. Los sensores y actuadores  son la forma en que la I.A. interactúa con el mundo físico. Muchas aplicaciones de la I.A. viven cómodamente en mundos virtuales procesando y generando datos que no provienen directamente del mundo físico. Pero en muchos otros casos las grandes inteligencias en la nube y los pequeños microcontroladores están condenados a entenderse.

Muchas veces ese problema se soluciona añadiendo conectividad a los microcontroladores y que se comuniquen con la nube. Eso da lugar a dispositivos extremadamente tontos que necesitan conexión a un servidor a miles de kilómetros para encender o apagar una luz o poner la calefacción.

No hay nada malo en que los dispositivos estén conectados. Permite su control y monitorización de forma remota. Lo que no tiene sentido es que el dispositivo no pueda actuar sin conexión a internet. Internet debe de ser una fuente de datos más para el dispositivo. Un termostato puede mirar en internet la previsión del tiempo o el precio de la luz y usarlos como complemento para ajustar la temperatura de la casa pero sin ellos debes de seguir funcionando.

La inteligencia artificial es un área que se avergüenza de sus pasado. Tanto como para dejar de llamar «inteligencia artificial» a sus éxitos anteriores en cuanto aparece un nuevo enfoque. Por ejemplo la visión por computador de hace unos años está llena de algoritmos para reconocer formas, seguir objetos, calcular bordes….llegaron las redes neuronales convolucionales y todo eso quedó en un segundo plano.

Son algoritmos válidos y eficaces, no tan flexibles como el deep learning pero lo poco que hacen lo hacen suficientemente bien. Muchos de esos algoritmos se usan en entornos industriales. Lo mejor de todo es que los microcontroladores actuales pueden ejecutarlos. Podemos tener chips diminutos, de bajo consumo y barato ejecutando algoritmos que llevan décadas siendo usados (y por tanto mejorados) para realizar tareas que requieran cierta cantidad de inteligencia o de aprendizaje.

Si los microcontroladores no son suficientemente potentes los ordenadores también han bajado de precio y hay SOC que pueden actuar de cerebro cuando se requiera más potencia de calculo sin necesidad de abandonar tu red local. Y aún si no fuera suficiente, por ejemplo para reconocimiento de voz, se puede consumir como si fuera un servicio.

Lo ideal seria una arquitectura en la que los micontroladores fueran capaces de realizar las tareas básicas sin ayuda externa, por encima de ellos un hub que conecta y coordina los dispositivos y si aun asi se requiriera alguna tarea demasiado pesada para el hub se puede consumir como servicio. La idea de este modelo es que se «quede en casa» tanta parte del sistema como sea posible.

Ahora que tenemos una idea de la arquitectura ideal centrémonos en la inteligencia embebida dentro los microntroladores, se enfrenta a la siguientes limitaciones:

  • Memoria RAM, suele ser poca y hay que gestionarla bien.
  • Potencia de cálculo, los microcontroladores no están diseñados para grandes cálculos por lo que tienen importantes limitaciones.
  • Tiempo real, por lo general no pueden esperar demasiado a dar respuesta. Un sistema de seguridad no puede tardar un cuarto de hora en decidir si da la alarma.
  • Bateria, muchos dispositivos funcionan con baterías lo cual complica todo un poco más y que obliga a aplicar medidas para reducir el consumo y alargar su duración.
  • Poco espacio para modelos, los microcontroladores no tienen «un disco duro» donde almacenar grandes cantidades de datos. Eso limita el tamaño de los modelos y bases de datos a usar.

En resumen los algoritmos de inteligencia artificial que se llevan usando décadas pueden seguir siendo útiles en dispositivos de domótica en lugar de depender de sistemas en la nube.

Secreto compartido entre dos usuarios para Arduino

¿Sabéis esas películas en las que hay que lanzar una cabeza nuclear y dos personas han de meter su código, ya sea a mano o con una llave, para autorizarlo?. Eso es el secreto compartido. Una contraseña que está «dividida» en partes que tienen que juntarse para poder usarse. Hay esquemas de secreto compartido que permiten configuraciones mucho más flexibles, pero también son más exigentes en cuanto a recursos. Para el caso más simple de una contraseña y dos partes este sistema es suficiente.

Veamos cómo es el sistema. Tenemos una contraseña K que lanza los misiles. Está contraseña es dividida en dos contraseña K1 y K2. Al juntarlas se obtiene la contraseña K. Para que el sistema sea seguro ambas han de ser igual de difíciles de adivinar que K y cualquiera de ellas por separado no ha de aportar información que reduzca la dificultad de adivinar K.

Lo primeros que pensaréis será: «Tomamos la contraseña (K) de un tamaño de N bits y la dividimos en otras dos (K1,K2) de tamaño N/2 y listo». Es cierto que funciona, pero tiene varios problemas, el principal es que estás desvelando información sobre K a quien posea una de estas partes.

Veamos otra aproximación. Generamos una contraseña K1 aleatoria de N bits. Está claro que K1 no aporta ninguna información sobre K, aunque por ahora tampoco nos sirve de nada. Será K2 quién los relacione. Para ello realizamos una operación xor entre K1 y K bit a bit.

Los poseedores de K1 no tienen ninguna pista que les ayude a calcular K o K2. Y lo mismo para quién posea K2.

Veamos un ejemplo:

K = 00110100
K1 = rand() = 10011011
K2 = K xor K1 = 10101111

K1 xor K2 = 00110100 = K

Una de las ventajas de este sistema es que puedes crear tantas parejas de claves como quieras.

Es poco probable que tengas armamento nuclear en casa. Pero esta idea es aplicable sobre cualquier sistema que use contraseñas.

Máquina de estados finitos en Arduino

Vamos a ver cómo crear una máquina de estados finitos muy sencilla para Arduino o Node MCU. Debido a las limitaciones de ambas plataformas vamos a ver una idea muy sencilla.

Empezaremos por aclarar los conceptos básicos:

  • Estados: son los distintos valores que puede tomar la máquina de estados finitos.
  • Eventos: producen el cambio de un estado a otro.
  • Transiciones: definen como los eventos cambian los estados. Cada transición constan de tres elementos, un evento, un estado origen y un estado destino. Indica que cuando se produzca el evento si el estado actual es el estado origen este cambiará por el estado destino.

Una mef (máquina de estados finitos) está siempre en un único estado y hay eventos que producen el cambio de estado siguiendo unas reglas (las transiciones). Aunque con esto ya tenemos una mef, para que sea realmente útil tiene que facilitar la ejecución de funciones asociada a los distintos estados y eventos.

Para facilitar todo esto podemos usar la librería easy finite state machine que simplifica la programación usando macros del preprocesador.

Estados y eventos

Vamos a empezar por los eventos y estados, para ello podemos usar enum de C que nos permite escribir código de forma muy intuitiva.

enum efsmStates {start, step1, step2, finish};
enum efsmEvents {next, back};

O usando las macros de la libreria:

STATES {start, step1, step2, finish};
EVENTS {next, back};

También habrá que definir una variable que guarde el estado actual e inicializarla con el estado inicial.

enum States efsmState = start

Con la librería:

INIT(start)

Cambiar el valor del estado es muy sencillo, basta con hacer:

efsmState = start

O con las macros:

changeState(start)

Así como comparar el valor del estado:

isState(start)

Transiciones:

De todas formas lo habitual no es realizar los cambios de estados directamente sino a través de eventos. Los eventos vamos a gestionarlos con la función efsmEvent(event) que los aglutina todos. Esta función recibe el evento como parámetro y contiene las transiciones. Básicamente son un montón de «if». Usando la librería se usa la macro TRANSITION

TRANSITION(evento, estadoOrigen, estadoDestino, funcion)

Su significado es: Si se lanza el evento y el estado es el estadoOrigen se pasa al estadoDestino y se llama a función. No es necesario poner una función asociada, se puede dejar en blanco.

Las transiciones se definen entre las macros:

START_TRANSITIONS y END_TRANSITIONS

Las transicione son una gran ayuda, aunque su funcionamiento se puede sustituir con isState(state) y changeState(state).

if(isState(estadoOrigen) ){
  changeState(estadoDestion);
  funcion();
}

Ejecuciones:

Cuando se ejecuta efsmExecute() verifica y llama a la función asociada a cada estados. Es decir, cuando se llama a esta función se verifica en qué estado está la máquina y llama a la función asociada a ese estado. Con la forma que tiene Arduino de funcionar usando una función loop que está en bucle constante la idea es colocar efsmExecute() en la función loop() para que se llame en cada iteración.

EXECUTION(executionState,function())

Si no se asocia ninguna función a un estado no se ejecutará nada.

Se definen entre las macros:

START_EXECUTIONS y END_EXECUTIONS

No es obligatorio definirlas. Por ejemlo cuando la mef solo tenga que lanzar funciones en los cambios de estado (transiciones).

Disparadores:

Los disparadores generan eventos asociados a distintos sucesos. Su idea es facilitar la programación de acciones habituales que lanzan eventos. Se ejecutan cuando se llama a la función efsmTriggers().

Para que un disparador se lance, el estado de la mef ha de ser el mismo que el que se le pasa como primer parámetro. Hay de tres tipos de disparadores:

Un condicional lanza un evento si el condicional especificado se cumple:

CONDITIONAL(state, condition, event)

Un contador lanza un evento después de haber llamado un número determinado de veces a la función efsmTriggers() tras al último evento válido.

COUNTER(state, number, event)

Un temporizador lanza un evento cuando ha pasado un determinado tiempo (en milisegundos) desde que se lanzó el último evento válido.

TIMER(state, number, event)

«Tras el último evento válido’ significa que cada vez que se lanza un evento y este produce una transición los contadores se reinician. Si se quisieran reiniciar a mano se pueden usar: resetTimer() y resetCounter()

Los disparadores se definen entre las macros:

START_TRIGGERS y END_TRIGGERS

No es necesario definir disparadores , si se quiere lanzar algún evento manualmente se puede hacer llamando directamente a efsmEvent(event).

En resumen:

  1. Se definen estados y eventos.
  2. Se establece el estado inicial.
  3. Se crean transiciones que indican que cambios entre estados se producen con cada evento asi como la función que se lanzara cuando se produzca esa transición.
  4. Se escriben la ejecuciones indicando que función se lanzará cuando la máquina este en cada estado.
  5. Se declaran los disparadores que lanzaran eventos (o se programa el lanzamiento a mano).
  6. Se añade a la función loop efsmExecute() y efsmTriggers()

Un ejemplo:

Para ver todo un poco más claro vamos a ver un ejemplo basado en el que incluye la librería. En el se modela la máquina de estados finitos representada en el siguiente diagrama:

Ejemplo de maquina de estados finitos
Máquina de estados finitos del ejemplo

El código es el siguiente:

#include <efsm.h>

STATES {start, step1, step2, finish};
EVENTS {next, back};
INIT(start)

START_TRANSITIONS
TRANSITION(next,start,step1,Serial.println("next")) //next start -> step1
TRANSITION(next,step1,step2,Serial.println("next")) //next step1 -> step2
TRANSITION(next,step2,finish,Serial.println("next")) //next step2 -> finish
TRANSITION(back,step2,step1,Serial.println("back")) //back step2 -> step1
TRANSITION(ANY_EVENT,ANY_STATE,ANY_STATE,Serial.println("FAIL!!!")) //in any other case
END_TRANSITIONS

START_EXECUTIONS
EXECUTION(start,Serial.println("start")) //state is start
EXECUTION(step1,Serial.println("step1")) //state is step1
EXECUTION(step2,Serial.println("step2")) //state is step2
EXECUTION(finish,Serial.println("finish")) //state is finish
END_EXECUTIONS

START_TRIGGERS
COUNTER(start,5,next); //State start wait 5 iterations and launch event next
TIMER(step1,2000,next); //State step1 wait 2 sg and launch event next
END_TRIGGERS

void setup() {
  Serial.begin(9600);
  resetTimer();
  resetCounter();  
}

void loop() {  
  efsmExecute();
    
  if(isState(step2)){
    efsmEvent(next);
  }
  efsmTriggers();
  
  delay(1000);
}

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Localización en interiores usando redes WiFi

Ya hemos visto que usar la distancia al emisor para posicionarte dentro de casa es muy difícil, al menos sin usar hardware especializado. ¿Nos hemos quedado sin opciones?. Somos gente ingeniosa, no nos quedamos sin opciones. Si no podemos usar la distancia por la gran variación que tiene la potencia de la señal en interiores, le daremos la vuelta al problema y lo usaremos como ventaja. Si a la gran variación que hay en la potencia de la señal le sumamos la gran cantidad de nodos WiFi que hay alrededor nuestro, resulta muy poco probable que en dos puntos que no estén muy próximos se den los mismos valores para todas las redes.

Vamos a empezar por la idea básica y luego desarrollaremos los distintos problemas que surgen en la vida real.

Primero vamos a movernos por nuestra casa midiendo la potencia de las redes WiFi que se reciben en distintos puntos que consideramos interesantes para localizarnos. Obtendremos una lista de tuplas {SSID, potencia} para cada punto.

Una vez tengamos esa base de datos creada vamos a tratar de saber lo «cerca» que estamos de esos puntos sabiendo solo la potencia de los wifi que nos rodean. En este caso «cerca» no se refiere a distancia física en metros si no a lo parecidas que son las mediciones en dos puntos. Aunque no tenga que ver con la distancia física a este concepto también se le llama distancia.

Para medir esta distancia es fundamental saber que valor o valores vamos a usar, en este caso la potencia de la señal del WiFi. Vamos a compararla con las potencias almacenadas en nuestras mediciones anteriores. Para ello tomaremos las potencias medidas en los nodos con el mismo SSID y las compararemos, sumando luego todas para saber como de «próximos» están esos dos puntos.

Siendo Pi[ssid] la potencia en la base de datos para el nodo con ese SSID en el punto i y Pm[ssid] para la potencia medida de ese mismo nodo la distancia seria:

Dist(Pi) = ∑ √ (Pi[ssid] – Pm[ssid])²

O una forma menos exacta pero también computacionalmente menos costosa (y que a mi me a servido perfectamente)

Dist(Pi) = ∑ ABS(Pi[ssid] – Pm[ssid])

Siendo ABS la función que calcula el valor absoluto.

Que la i no despiste, en ambas formulas el sumatorio se refiere a sumar la diferencias de los distintos SSID medidos de cada punto.

Podemos determinar de que punto estamos más cerca comparando estas distancias y quedándonos con aquel punto Pi cuyo valor sea menor.

Para aplicar esta idea dividí mi casa (realmente solo dos habitaciones contiguas y un trozo de pasillo para probar) en una matriz de cuadrados de un metro de lado y establecí comos puntos de referencia el centro (a ojo) de cada cuadrado.

¿Funciono bien? La sorpresa es que, tras corregir problemas de los que hablo más adelantem mucho mejor de lo que esperaba. Buscando los puntos más cercanos me resultaba fácil saber donde estaba. No siempre me daba como más cercano el punto del que más cerca estaba pero nunca se fue muy lejos, así que podía saber mi posición con bastante precisión.

Problemas

Desgraciadamente de la teoría a la practica suele haber un camino lleno de baches. Por lo que siempre surgen problemas para aplicar la teoría tal cual y que hay que resolver.

Nodos que aparecen y desaparecen

Si los nodos WiFi que usas como referencia están siempre encendidos el hecho de que un nodo no esté entre tus mediciones indica que potencia es 0 y es un gran indicador de donde estamos. Pero si usas los WiFis de tus vecinos el que se apaguen a lo largo del día es algo más habitual de lo que parece. Por lo que tenemos nodos que aparecen y desaparecen. Así que si un nodo no esta entre tus mediciones no se puede considerar que su potencia es 0 (aunque lo sea en ese momento) ya que puedes estar falseando los resultados. Mi experiencia es que si no tienes medición de un nodo lo descartes y no lo tengas en cuenta en tus cálculos.

Ruido en las mediciones

Los valores de potencia pueden variar muchísimo al realizar una medición, mi consejo es que realices varias medidas (yo uso 3) y te quedes con la mediana o la moda de las mismas. Es muy importante sobretodo que realices esto al generar la base de datos de los puntos de referencia.

Otro problema es que no todas los nodos se ven sometidos a las mismas variaciones, contra menor potencia tengan en ese punto más propensas son a sufrir variaciones y más amplias son estas. En algunos casos resulta conveniente fijar un umbral mínimo de potencia para tener en cuenta ese nodo.

Variación de las medidas usando distintos aparatos.

Es recomendable usar el mismo «aparato» para generar la base de datos de puntos que el que luego se va a intentar localizar. Las medidas entre mi portátil, mi smartphone y mi placa Node MCU eran suficientemente distintas como para causar errores si intentaba comprar unas con otras

Guardar datos de nodeMCU (o arduino) en la nube usando IFTTT

La idea es usar IFTTT, que permite interconectar varios servicios, para que nos sirva de repositorio de datos de nuestros sistemas IoT desarrollados con arduino, esp8266, nodeMCU o similares. ¡Y además gratis!.

Webhooks

Nuestro sistema enviara una petición HTTP (GET o POST) al servicio de IFTTT para ello hay que crearse una cuenta allí y activar un evento por webhook, que recibirá los datos que le enviemos.

Los webhook de IFTTT solo permiten pasar hasta tres valores que ademas tiene que ser value1, value2 y value3.

Incluyo un ejemplo de enviar un petición con nodeMCU con 3 valores:

 
void sendMsg(){ 
  http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]?value1=1&values2=2&value3=3"); //HTTP 
  int httpCode = http.GET(); 
  if(httpCode > 0){
    Serial.println("Alarm send"); 
  } else {
    Serial.println("Error send alarm"); 
  } 
  delay(1000); 
}
 


IFTTT recibe un nombre de evento, tres parámetros y el timestamp de cuando ha ocurrido el evento:

{{OccurredAt}}
{{EventName}}
{{Value1}}
{{Value2}}
{{Value3}}

Okey tenemos esos cinco parámetros ¿Que podemos hacer con ellos?

El que incluya la hora del evento es muy útil ya que muchos sistemas no tiene un reloj y no pueden saber que hora es.

Hay que tener en cuenta es si tenemos una sola placa emitiendo datos o tenemos varias. Si tenemos varias podemos usar el nombre del evento o uno de los valores para indicar que placa es, es algo muy útil sobre todo cuando una de ellas empieza a dar datos erróneos así resulta fácil filtrarlos.

Puede parecer que un nombre de evento y tres valores son pocos pero para muchos sistemas de IoT son mas que suficientes.

Hoja de cálculo

Mi primera idea es guardar los datos que nos interese en una hoja de calculo de Google. IFTTT nos permite guardar los datos en filas, con una columna cada datos. Hasta 2000 filas, luego no deja de guardarlas, pero crea otra hoja de cálculo.

La ventaja de este sistema es que podemos conectarlo para que genere tablas, gráficas o realice cálculos de forma automática. Con poco trabajo nos podemos montar un sistema que almacene y procese lo datos generando informes y gráficas todo ello gratis.

Fichero de texto

Otra opción es usar un fichero de texto y almacenar los datos estilo CSV, que consiste en separar los valores por comas y ya esta. Se pueden usar varios, en mi caso opte por Dropbox. La ventaja de este tipo de ficheros es que es muy sencillo de procesar por cualquier dispositivo. De hecho hay tres maneras de hacerlo con Dropbox.

  • Sincronización: tener Dropbox sincronizado en el dispositivo con lo que el fichero de actualiza «solo» con cada cambio
  • API: usar la API de Dropbox para acceder a ese fichero
  • Publico: Dropbox permite crear enlaces públicos a ficheros. Una vez generado ese enlace leer es fichero es tan sencillo como hace una petición GET

En el caso de este tipo de ficheros el limite de IFTTT esta en 2 megas de datos.

Redes sociales

Si los datos han de ser compartidos con otras personas, las redes sociales pueden ser una buena solución, por ejemplo una cuanta de Twitter (privada si no quieres que los datos sean accesibles por todos). Además también se puede automatizar el acceso a los datos usando la API de Twitter

Notificaciones

Si los avisos tienen que llegar en tiempo real (o casi) se pueden usar las notificaciones a móvil (ya hice un ejemplo) o si tienen que llegar a un grupo de gente se pueden notificar en un grupo de Telegram. Este mismo sistema se puede usar para notificaciones personales si quieres guardar un histórico de ellas.

Calendario

Este caso es solo una idea, pero en el caso de eventos que ocurran solo de vez en cuando y se quiera tener un registro de cuando ocurrieron se podría usar la integración con alguno de los calendarios.

No resulta útil para eventos muy numerosos ya que es difícil de consultar cuando hay muchos en un día

Alarma con nodeMCU y un radar HW-MS03

La razón oficial de este montaje era crear un sistema de alarma con sensor de movimiento que cuando detectase algo me enviara un notificación al movil. La razón real es que me habia comprado un nodeMCU y un HW-MS03 y algo tenia que hacer con ellos.

En principio el proyecto es sencillo. Conectar el radar al nodeMCU y cuadno detecte movmiento enviar una notificación através de un webhook de IFTTT para que apararezca una notificacion en el movil. Pero resulta que nada es tan sencillo como parece, pero asi es más divertido.

Lo primero es concetar el HW-MS03 al nodeMCU lo cual es facil porque solo tiene tres pines.

HW-MS03 nodeMCU Funcion
GND GND Tierra
Vin 3V Alimentacion +3V (ojo no soporta +5V)
Out A0 Señal del HW-MS03 al nodeMCU

Cuando el HW-MS03 detecta algo que se mueve delante suyo (en teoria hasta 4 metros de distancia) manda la señal por el pin out. Basta con detectarla y realizar un http get contra la direccion que facilita IFTTT, facil. He usado al entrada analogica (A0) del nodeMCU porque no estaba segura si el voltaje de la patilla Out bastaria para activar las entradas digitales. Tras varias pruebas he fiajado el valor de umbral en 800.

if(analogRead(A0) > 800){
    ...
}

Pero volvamos un paso atras. resulta que concetar juntos ambas placas fue una mala idea, ambas trabajan en la misma frecuencia. Asi que cuando el nodeMCU esta usando el WiFi el HW-MS03 se vuelve loco. Tras realizar varias pruebas puede ver que esto solo pasa cuando la distancia entre ambos era de unaos 50 cm, pero con cables de esa longituda tambien tenia problemas para leer el estado del pin. La unica opción que me quedaba era encender el WiFi solo para enviar la notificación cuando detectase movimiento. Para ellos hice dos funciones una aque apaga el WiFi y otra que lo conecta. (En realida si que tenia otra opción usar un PIR en lugar de un radar y ya no tendria problemas con el WiFi pero seria menos divertido)

void onWiFi(){  
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, password);

  // Wait for connection
  while((WiFiMulti.run() != WL_CONNECTED)) {
    delay(500);
  }
}

void offWiFi(){
  WiFi.disconnect(); 
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
}

Por lo que caundo se detecte movimiento habra que conectar el WiFi, enviarle mensaje y deconectarlo para volver a tener la alarma lista.

if(analogRead(A0) > 800){
  Serial.println("Alarm!!!!!!");      
  onWiFi();
  sendMsg();        
  offWiFi();
  firstStart = true;
}

firstStart es una variable booleana cuya funcion es dar tiempo «a correr» una vez conectada la alarma ya que si no saltaria al detectarte a ti mismo nada más activar la alarma. Tambien permite un tiempo de «descanso» tras el disparo de la alarma para evitar estar dando alarmas sin para y saturar el movil de notificaciones.

if(firstStart){
  Serial.println("Time to hide");    
  delay(60000);
  Serial.println("Watching...");
  firstStart = false;    
}  

Vamos aver como es el codigo que envia la notificacion. He optado por usar el servicio de IFTTT para ello hay que crearse una cuenta alli y activar un webhook, luego se crea una regla que lance una notificacion en el movil. O si se prefiere cualquiera de la opciones que disponibles.

void sendMsg(){
   http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]"); //HTTP

  int httpCode = http.GET();

  if(httpCode > 0){
    Serial.println("Alarm send");      
  } else {
    Serial.println("Error send alarm");     
  }

  delay(1000);
}

Mientras el nodeMCU este transmitiendo el radar queda inutilizado.

El codigo completo es:

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ESP8266HTTPClient.h>

const char* ssid = "[tu ssid]";
const char* password = "[password]";

bool firstStart = true;
unsigned long timeNewMeasure = 0;

HTTPClient http;
ESP8266WiFiMulti WiFiMulti;

void setup(void){  
  Serial.begin(115200);
  offWiFi();
  //timeNewMeasure = millis()+1000;
}

void offWiFi(){
  WiFi.disconnect(); 
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
}

void sendMsg(){
   http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]"); //HTTP

  int httpCode = http.GET();

  if(httpCode > 0){
    Serial.println("Alarm send");      
  } else {
    Serial.println("Error send alarm");     
  }

  delay(1000);
}

void onWiFi(){  
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, password);

  // Wait for connection
  while((WiFiMulti.run() != WL_CONNECTED)) {
    delay(500);
  }
}

void loop(void){
  if(firstStart){
    Serial.println("Time to hide");    
    delay(60000);
    Serial.println("Watching...");
    firstStart = false;    
  }  
  if(millis() > timeNewMeasure){
    timeNewMeasure = millis()+100;
    if(analogRead(A0) > 800){
      Serial.println("Alarm!!!!!!");      
      onWiFi();
      sendMsg();        
      offWiFi();
      firstStart = true;
    }
  }
}

Trilateralización

Tras que medir la distancia al emisor no diera el resultado deseado pensé en no escribir esta entrada, pero como puede ser útil y ya la tenia medio escrita he decidido terminarla.

Ya hemos visto cómo calcular (más o menos) la distancia a un emisor. Si conocemos la situación del emisor podemos situarnos en algún punto de una circunferencia de radio r1 (r1 es igual a la distancia al emisor). Si añadiéramos un segundo emisor de coordenadas conocidas y al que calculamos la distancia r2. Esto nos sitúa en una segunda circunferencia que corta a la primera en dos puntos. Ahora ya sabemos que estamos situados en uno de esos dos puntos. Para saber en cual exactamente necesitamos un tercer emisor cuya posición conozcamos y del que podamos calcular la distancia r3. Esta tercera circunferencia coincidirá con las otras dos en un solo punto. En la imagen se ve mejor

Trilateration.svg
De Braindrain0000 de la Wikipedia en inglés, CC BY-SA 3.0, Enlace

Ahora toca ponerlo todo en lenguaje matemático. En lugar de usar circunferencias vamos a usar esferas, los cálculos son los mismo y nos permite no tener los tres emisores y el receptor en el mismo plano. La ecuación de una esfera cuyo centro está en las coordenadas (a,b,c) es:

(x ─ a) 2 + (y ─ b) 2 + (z ─ c) 2= r2

Esta ecuación representa a todos los puntos de la súperficie de una esfera. Cómo tenemos tres esferas que coinciden en un punto de sus superficies hay que buscar un punto (x,y,z) que cumpla las ecuaciones de las tres esferas.

Esfera1, vamos a ser amables con nosotros mismo y vamos a colocar esta esfera en el origen de coordenadas (0,0,0) por lo que su ecuación es:

x 2 + y 2 + z 2 = r12

Esfera2, también por simplificar nos la vida, colocamos el centro de esta esfera en línea con la anterior en las coordenadas (d,0,0). Su ecuación es:

(x – d)2 + y 2 + z 2 = r22

La tercer esfera la colocamos con el centro en el mismo plano pero con ambas coordenadas distintas.

(x – i)2 + (y – j) 2 + z 2 = r3

En el articulo de la wikipedia esta la resolución completa de las ecuaciones paso a paso, yo salto a la conclusión:

x = (r1 2 – r2 2 + d 2 ) / 2d

y = (r1 2 – r3 2 + i 2 + j 2) / 2j + (x * i / j)

z = √(r1 x 2 – y 2 )

Con estos cálculos ya tienes localizado el punto exacto. Desgraciadamente en la vida real las mediciones no suelen ser tan exactas por eso la mejor solución es detectar donde cortan dos circunferencias (por ejemplo la 1 con la 2 y la 1 con la 3) Buscar los puntos mas cercano y tomar el punto medo entre ambos, teniendo en cuenta que es una aproximación y que no será el punto exacto.

Medir distancia al emisor WiFi.

La idea es sencilla, mides la potencia de la señal de varias estaciones cuya posición conoces, con la potencia calculas la distancia a cada estación y a partir de ahí con geometría básica calculas tu posición. Parece fácil, hasta que lo intentas y vas viendo como todo se complica.

Como estación vamos a usar un emisor WiFi, eso nos permitirá realizar pruebas fácilmente ya que podemos usar cualquier router WiFi como estación y como receptor cualquier móvil. Vamos a empezar por lo mas sencillo, medir la distancia a un solo nodo cuya localización conocemos. En nuestro receptor podemos ver la potencia. ¿Como convertimos la potencia en distancia?. Las malas noticias es que no es lineal. Las buenas es que hay fórmula (Free-space path loss) para hacerlo de manera aproximada

dB = 20log10(d) + 20log10(f) + K

K varia segun las unidades de medida de la distancia d y la frecuencia f, siendo:

  • K = 92.45 si d está en km y f en Ghz
  • K = -87.55 si d está en m y f en Khz
  • K = -27.55 si d está en m y f en Mhz
  • K = 32.45 si d está en km y f en Mhz

Lo que nos interesa son metros y Mhz. Como frecuencia de la seña WiFi se puede usar siempre 2400 Mhz sin temer cometer un gran error (aunque ya hay algunas redes que operan a otras frecuencias como 5 Ghz)

Aunque no es del todo exacta en muchas aplicaciones usan directamente esta formula para calcular la distancia. En el receptor podemos conocer  la atenuación (dB), la frecuencia (f) y la constante (K). Ahora podemos resolver la distancia (d).

dB – (20log10(f) + K) = 20log10(d)

Cómo ni f ni K van a variar en nuestro caso vamos a remplazarlos por una constante C

dB – C = 20log10(d)

Despejando d

d = 10^((dB – C)/20)

Si queremos ser mas exactos no basta con leer la potencia de señal que se recibe en el receptor ya que la formula esta expresada en decibelios (dB) y la potencia de la señal WiFi nos la devuelve el sistema en decibelios-miliwatio (dBm). Que aunque aparentan ser lo mismo no lo son. Los dB son una medida relativa de potencia entre dos valores (en este caso serian la potencia de emisión del nodo WiFi y la potencia percibida en el receptor). Sin embargo los dBm indican un valor de potencia expresado en relación a 1 mW. Que para los que no estamos acostumbrados a este campo todo esto nos suena a chino y lo único que queremos saber es: ¿Como se convierte de una unidad a la otra?. Pues no es tan sencillo porque cada unidad mide cosas diferentes. Voy a tratar de explicarlo para este caso y perdonarme si meto mucho la pata, ya os digo que no es un tema que domine. Los dB serian la diferencia de potencia entre la señal original y el punto donde estas recibiéndola y se calcula como:

dB = 10log10(P/P0)

Siendo P la potencia medida y P0 la emitida o de referencia. Necesitamos obtener esas dos potencias. Para ello podemos usar los dBm, que son los dB medidos tomando como potencia de referencia 1mW por lo que la formula queda:

dBm = 10 log10(P/1mW)

Podemos calcular P en mW fácilmente.

P = 10 ^ (dBm/10)

¿Pero de donde sacamos P0?. Los que esperéis complicados cálculos siento decepcionaros. La forma de calcular P0 (de forma aproximada) es acercarse lo más posible al emisor y medir la potencia en dBm, despejar la formula anterior y usar ese valor como P0.

Con todo esto podemos calcular el valor en dB de la atenuación de la señal. Así que ahora podemos volver a la primera formula para calcular la distancia.

En javascript

function dBm2dB(dBm0, dBm){
  var P0 = Math.pow(10, dBm0/10);
  var P = Math.pow(10, dBm/10);
  var dB = 10 * Math.log10(P/P0);
  return dB;
}

function fspl(dB,f,K){
  var logF = 20*Math.log10(f) + K;
  var d = Math.pow(10, (Math.abs(dB) - logF)/20);
  return d;
}

//ejmeplo
fspl(dBm2dB(-29, -58), 2400, -27.55)*10;

¿Que tal resultado da? Pues por alguna extraña metedura de pata que no logro localizar necesito multiplicar el resultado por 100 para que las cifras tengan sentido. La aproximación es buena pero no lo suficiente, incluso sin moverse los valores de la señal varían mucho. Como sistema de localización, la potencia de la señal WiFi deja mucho que desear