Optimizar la comparación de distancias entre varios puntos

La función distancia se usa muy a menudo en muchos algoritmos para calcular la distancia entre puntos en el espacio. Hay algoritmos que hacen un uso intensivo de esta función como k-nn, k-means, o casi cualquier método que incluya buscar puntos cercanos. Por lo que reducir el coste de calcularla reporta una mayoría considerable de rendimiento.

Distintas distancias

Por suerte podemos usar funciones distancia diferentes. Una función distancia tiene que cumplir las siguientes condiciones cuando
x != y:

dist(x,x) = 0
dist(x,y) != 0
dist(x,y) = dist(y,x)
dist(x,y) <= dist(x,z) + dist(z,y)

Para calcular la distancia entre dos puntos es habitual usar la distancia euclídea:

dist(A,B) = √∑(Ai-Bi)²

Podemos hacer una primera mejora, si solo necesitamos comparar distancias podemos ahorrarnos la raíz cuadrada. Obtenemos una función distancia perfectamente válida y que puede reemplazar la euclídea

Pero hay una mejora más eliminar los cuadrados de las restas. Sin embargo hay un detalle a tener en cuenta, el cuadro de un número negativo es positivo, por lo que no basta con eliminarlos hay que reemplazarlos por el valor absoluto.

Esta parece un mejora menor pero en caso de espacios de muchas dimensiones puede llegar a notarse.

Pero no somos los primeros en llegar a esta conclusión, es conocida como distancia Manhattan:

dist(A,B) = ∑|Ai-Bi|

Realmente podemos reemplazar la distancia euclídea por cualquier distancia (tiene que cumplir los cuatro puntos anteriores) siempre que la nueva distancia cumpla que si en la distancia euclídea la distancia entre dos puntos cualesquiera A y B es mayor (o menor) que entre dos puntos cualesquiera C y D, en la nueva distancia también ha de ser mayor (o menor) en la nueva distancia.

Desigualdad triangular

Viendo la cuarta propiedad de las funciones distancia, conocida como desigualdad triangular, podemos usarla para acelerar la comparación entre distancias.

Teniendo tres puntos A,B,C sabemos que:

dist(A,B) + dist(B,C) >= dist(A,C)

|dist(A,B) – dist(B,C) | <= dist(A,C)

Es decir la suma de dos lados de un triángulo es mayor o igual que el tercer lado y el valor absoluto de la resta de dos lados es menor o igual que el tercer lado.

En la imagen de debajo de este texto se puede ver ejemplos visuales:

Operaciones entre distancias

Esto nos sirve para acotar distancias sin calcularlas. Calculando solo dos distancias podemos acotar el valor mínimo y máximo de la tercera lo cual nos puede permitir descartarla sin calcularla.

Por ejemplo, nos dan un punto C y tenemos que averiguar qué otro punto está más cerca A o B. A y B ya los conocíamos así que tenemos precalculada dist(A,B). Calculamos dist(B,C), si dist(B,C) < |dist(A,B) – dist(B,C)| entonces podemos decir que dist(A,C) > dist(B,C) ¡Sin calcularla!

En muchas situaciones tenemos puntos que son previamente conocidos/aprendidos. Por lo que podemos tener una tabla de distancias entre puntos precalculadas y usarlas para acotar distancias sin necesidad de calcular la distancia entre todos los puntos.

El problema es que una tabla de distancias entre puntos puede ocupar demasiado espacio en memoria. Veamos una alternativa, calcular la distancia de todos los puntos al mismo punto. Si no hay muy buenas razones para usar otro punto usaremos el origen de coordenadas (el punto cuyas coordenadas son todas ceros). Es muy poco costoso calcular la distancia de origen a un punto P

dist(O, P) =  √∑Pi² (euclídea)

dist(O, P) =  ∑Pi (Manhattan)

Ahora si tenemos precalculadas las distancias de los puntos a comparar con el origen y nos dan un nuevo punto P. Calculamos dist(O, P) y tenemos precalculado dist(O,A) y queremos saber dist(A, P).

Sabemos que:

dist(O, A) + dist(O, P) >= dist(A, P) >= |dist(O, A) – dist(O, P)|

Max dist(A, P) = dist(O, A) + dist(O, P) 

Min dist(A, P) = |dist(O, A) – dist(O, P)|

Ya tenemos una cota mínima y máxima de cual es la distancia de de A a P. Realmente por si misma no nos sirve de nada pero si hay que comparar con muchos puntos es suficiente pare descartar muchos de ellos.

Comparar distancias

Vamos a ver con una imagen la diferencia entre las tres distancias comentada en este texto. Euclídea, Euclídea² (sin hacer la raíz cuadrada) y distancia Manhattan. En la imagen inferior se puede ver la forma y el área que cubre una «circunferencia» de radio 2. (Tomando como circunferencia como aquel objeto geométrico cuyo todos sus puntos estan a la misma distancia del centro, en este caso 0,0)

El valor de tus datos

Vivimos en la época en que los datos son tan valiosos como el dinero. Es imposible desarrollar modelos de Big data o de aprendizaje maquina sin una gran cantidad de datos (el término «big data» puede ser una pista de lo importantes que son). Obtener estos datos es uno de los problemas más costosos de resolver a la hora de implementar estos modelos.

La necesidad de datos no termina ahí, para sacar rendimiento a estos modelos necesitan datos sobre los que aplicarlos. Imagínate que voy al banco a pedir un crédito y solo tienen mi nombre y apellidos, con eso su departamento de riesgos no va a poder evaluar cuanto «riesgo» supone darme un crédito. Necesitarán que rellene un formulario y les lleve varios documentos que les permitan valorar ese riesgo. En ese caso soy consciente de ello, pero en muchos otros no.

No sirve alimentar al sistema con datos a lo loco, estos datos necesitan tener algunas características:

Cantidad: se necesitan montones de datos para obtener un modelo. Obtenerlos es un proceso complicado y en algunos casos caro. Puede ser que no sea necesario recopilar los datos, que ya estén en bruto, pero que sea necesario «extraerlos». Incluso es posible que digitalizarlos.

Procesar estos datos tiene un coste, no basta con «acumularlo y lanzarlos a los algoritmos», necesitas saber que hacer con ellos y adaptarlos. Eso necesita un equipo de expertos detrás.

Calidad: los datos han de ser ciertos y útiles. O lo que es lo mismo han de tener la mínima cantidad de errores.

Esto hay que tenerlo en cuenta al procesar los datos. Según el método de recopilación puede haber errores al introducir los datos, al transcribirlos, malentendidos o directamente datos falsos. ¿Quién no ha mentido al rellenar un formulario?.

Relevancia: si tenemos muchos datos pero no podemos obtener conclusiones de ellos no nos sirven. Por desgracia es posible que durante la fase de recopilación de datos no se sepa muy bien cuáles son útiles o que surja una especie de «síndromes de Diógenes» de los datos y se recopilen todos los que se pueda. Como consecuencia asume que cuando usas una web o una aplicación todo lo que haces es recopilado.

Por ejemplo, saber el número de pie de todos los que entran a una tienda probablemente no nos resulte útil (quizás, si es una zapatería). Sin embargo lo que han comprado si.

Hay un tipo de datos especialmente valioso que sirve para cruzar entre varias fuentes. Un ejemplo es el número de teléfono móvil. Es un dato que no solemos revelar mucho de darlo al realizar compras por internet, encargos, rellenar formularios para tarjetas descuento, …. Sin embargo puede ser tremendamente valioso ya permite identificar al mismo individuo en distintas fuentes de datos y cruzarlas.

Variedad: de nada sirve tener muchos datos de gran calidad si pertenecen a una muestra pequeña de individuos. Es necesario tener muestras del mayor número de sujetos posibles.

Si los datos están sesgados el modelo también lo estará. Por ejemplo si obtienes los datos a partir de una aplicación de descuentos en el móvil estás dejando de lado a toda la gente que no use smartphones o no tengan suficiente soltura para usarlos o no quiera «instalar cosas raras» en ellos. Los datos de esas personas se vuelven más valiosos para completar el modelo. Quizás podría hacer un concurso donde se participe rellenando una papeleta con los datos personales.

Legales y éticos: es importante no recopilar datos que la ley prohíba recopilar o qué no sea ético hacerlo. Aunque es posible hacer trampas y usar proxys. Los proxys son datos que de forma directa o indirecta permiten «deducir» otros datos. A veces no son 100% exactos pero no importa los algoritmos pueden tolerar cierta cantidad de errores. Un ejemplo tan obvio que solo sirve como ejemplo sería no poder pedir el sexo pero si el nombre. Con el nombre se puede extraer el sexo en la mayoría de los casos. Hay veces que un proxy no es un solo dato sino varios.

Baratos: debido a la cantidad de datos necesarios es importante que obtener cada muestra sea barato. Eso puede condicionar el método elegido para recopilarlos e introducir sesgos.

Muchos datos que necesitan tratamiento manual son procesados por personas con sueldos bastante bajos.

Hasta este punto todo han sido costes. El modelo no dará beneficios hasta que empiece a usarse. Esto no es una justificación para no pagar por los datos, de hecho es probable que te hayan pagado en forma de descuentos, promociones, concursos. El problema es que no sabes que van a hacer con esos datos. ¿Y si se usan en un modelo que acaba siendo usado en perjuicio tuyo?

Una vez creado el modelo para obtener beneficio hay que usarlo. Para ello se necesita obtener tus datos para introducirlos al modelo y obtener un resultado. Lo que este resultado aporte al negocio (más conversiones a clientes, más compras, mejor servicio, ahorro de recursos y costes) es el beneficio que produce.

Cuando alguna empresa anuncie que ha desarrollado un sistema que le va a permitir ahorrar o ganar cantidades ingentes de dinero piensa que tus datos han sido necesario para ello.

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

Entropía

Supongo que la entropía os sonara de la termodinámica y así es. Cuando Shannon desarrollo la teoría de la información llego a una formula similar a la que describe la entropía en los sistema físicos. He aquí la formula:

H(x) = – Σ p(xi) * log2(p(xi))

Es decir, si tenemos una variable x con distintos valores posibles, para cada valor (xi) cuya probabilidad es p(xi) la entropía de ese valor es:

 p(xi) * log2(p(xi))

Y la entropía total es la suma de la entropía de cada valor. A veces a cada posible valor se le llama signo.

Por ejemplo supongamos un semáforo que se pasa el 45% del tiempo en rojo, el 45% en verde y el 10% restante en ambar. La entro pía de cada señal seria:

x p(x) Entropia Formula
verde 0,45 -0.156 log2(0,45)*0.45 
rojo 0.45 -0.156 log2(0,45)*0.45
ambar 0.1 -0.1 log2(0.1)*0.1

Entropia =  -0,412

¿Como se usa esto y qué mide?

Mide la cantidad de bits que «aporta» un dato o lo que es lo mismo, la cantidad de información que transmite. Para entenderlo mejor tomando el ejemplo más simple, un bit con dos valores (0,1) pero cambiando la probabilidad de cada uno, respetando que la suma de ambas siempre es 1 (P(1) = 1 – P(0)):

P(0) P(1) Entropia Formula
0 1 0 -(log2(0)*0 + log2(1)*1)
0.1 0.9 0.46 -(log2(0.1)*0.1 + log2(0.9)*0.9)
0.2 0.8 0.72 -(log2(0.2)*0.2 + log2(0.8)*0.8)
0.3 0.7 0.88 -(log2(0.3)*0.3 + log2(0.7)*0.7)
0.4 0.6 0.97 -(log2(0.4)*0.4 + log2(0.6)*0.6)
0.5 0.5 1 -(log2(0.5)*0.5 + log2(0.5)*0.5)

Para el primer caso que es que el bit siempre sea 1 el valor aportado es 0. ya que no aporta ninguna información. en resumen si sabemos que un signo es más probable que otro aporta menos información que si ambos son equiprobables. 

¿Para qué nos sirve todo esto?. De forma muy simple, cuando haya que elegir entre varias propiedades, la entropía puede ayudarnos a elegir que parámetro escoger

El corazón del aprendizaje máquina

Decenas de algoritmos de aprendizaje que van desde la sencilla regresión lineal a cosas tan complejas como las redes neuronales y casi todos (seguro que hay alguno que no solo por fastidiar) se basan en una misma y sencilla idea. Reducir al mínimo el error total de la función de aprendizaje.

Vamos poco a poco. tenemos una función f(x) que recibe una entrada de valores x = [x0,…,xn] y que tiene unos parámetros p = [p0,….,pi] que configuran la respuesta de la función. Esta función es la que aprende, el proceso de aprendizaje consiste en adaptar los parámetros p para aproximar la salida a una deseada.

Voy a ir al caso del aprendizaje supervisado que es el más sencillo de entender. Hay un conjunto de ejemplos de entrada x cada uno con una «respuesta» Y = [Y0,…,Um] asociada (la respuesta correcta). Lo ideal es que cuando a f se le pasa x devuelva una «respuesta» y = [y0,…,ym] esa respuesta sea lo más parecida posible a Y.

f(x) = y ≈ Y

Esto se ha de cumplir con todos los ejemplos que hay. Ajustamos p para que minimice la suma de los errores de cada muestra de aprendizaje.

min Σ error(y, Y)

  • f es la función que pretende usarse para aprender el modelo.
  • p son los parámetros de f cuyo valor se módica para adaptarse al modelo. Este cambio de valor es la parte que se aprende.
  • x son los valores que recibimos como entrada de los cuales nuestro algoritmo tiene que calcular y
  • Y es el valor real asociado a esos parámetros
  • y es la aproximación calculada por nuestra función f

Para calcular el error se pueden usar diversas funciones. Igual que métodos para aproximar los valores de p. Sin embargo el algoritmo será algo parecido a este:

Inicializamos p
Para cada x
y = f(p, x)
e = error(Y,y)
aproximar(p, e)

El error total no siempre se calcula sumando los errores de cada muestra, pero es algo habitual.

A veces no es tan sencillo y para cada muestra x no hay un valor Y con el que comparar los resultados. El valor puede ser una propiedad de todos los resultados obtenidos. Por ejemplo muchos algoritmos de «clustering» se basan en minimizar la distancia interna entre elementos del cluster y maximizar la distancia externa entre los clusters elegidos.

Todo esto se puede complicar bastante y cambiar algunos detalles, pero en el fondo de todo algoritmo de aprendizaje hay una función de optimización que trata de reducir el error.

Generar frases de forma automática a partir de plantillas

Ya hemos visto otras formas de generar frases. Esta forma de generar frases consiste en tener unas plantillas de las que sólo algunas partes son variables.

Por ejemplo:

Al abrir el cofre encuentras un ${adjetivo} ${objeto} para ${acción}

Al usar una plantilla las frases generadas tienen sentido y es fácil integrarlas con otras generadas de la misma forma. Los textos generados no tienen tanta variedad como con otros métodos pero te aseguras el sentido de los mismos.

Hay un curioso proyecto en inglés que partiendo de una base de datos de palabras en inglés te permite generar un frase a partir de un aplantilla en la cual solo indicas {{an_ adjetive}}, {{noum}} y el lo rellena con palabras al azar de la base de datos (de términos terroríficos al parecer)

Por ejemplo:

This is {{ an_adjective }} {{ noun }}. => This is a wicked cadaver.

En castellano el principal problema que tiene este método son  distintas flexiones que tiene las palabras y la necesidad de que las distintas palabras de la frase tengan concordancia entre ellas. «Caperucita rojo» es claramente incorrecta. Así que nosotros tenemos que aportar género y número para nombres, complementos, adjetivos. Y en el caso de los verbos persona, tiempo y modo. Para solucionar este problema hay tres opciones:

  • Limitar las opciones, generamos frases solo con un género, número, tiempo y modo.
  • Crear diccionarios diferentes para cada opción. Por ejemplo para los nombres se crearían cuatro diccionarios: femenino-singular, femenino-plural, masculino-singular, masculino-plural.
  • Usar un algoritmo que modifique las flexiones de cada palabra Algo de eso ya hemos visto en este blog. Es un algoritmo al que le pasas una palabra y la forma de la misma que quieres obtener y te la transforma. Ya hemos visto algo así.

En JS generar este tipo de frases es muy sencillo, preparamos una plantilla.

‘Al abrir el cofre encuentras un ${objeto} ${adjetivo} para ${acción}`

Crearemos una lista de opciones:


var adjetivos = ["épico","dorado","brillante"];
var objetos = ["mazo", "mandoble", "arco"];
var acciones = ["matar dragones", "rescatar princesas", "luchar batallas"];

Ahora usaremos una función para elegir uno de ellos al azar:

function selectOne(items){
return items[Math.floor(Math.random() * items.length)];
}

Y finamente otra que rellena la plantilla:

function template(objeto, adjetivo, accion){
return `Al abrir el cofre encuentras un ${objeto} ${adjetivo} para ${accion}`
}

Vamos con el ejemplo de funcionamiento:

template(selectOne(adjetivos), selectOne(objetos), selectOne(acciones));

Ejemplo de resultados:

  • «Al abrir el cofre encuentras un brillante arco para luchar batallas»
  • «Al abrir el cofre encuentras un épico arco para matar dragones»
  • «Al abrir el cofre encuentras un dorado mazo para matar dragones»

Este sistema para generar frases tiene la ventaja de que estas tienen sentido pero tiene la desventaja de que resultan repetitivas y si se usa varias veces en seguida se percibe el patrón.

Un mejora necesaria seria usar un algoritmo de flexiones para encontrar el género y número del objeto y adecuar el del adjetivo. Imaginaros que uno de los objetos es «espada», el adjetivos debería ser femenino.

 

 

Generar frases de forma automática a partir de textos

Ya hemos visto, sin entrar en detalles, un sistema para generar frases en la entrada sobre los bots y las respuestas en lenguaje natural.

La solución más común para generar texto de forma automática es usar cadenas de Markov. Explicándolo pronto y mal consiste en tener un grafo para cada palabra, esa palabra está conectada con el resto de las posibles palabras que pueden seguirla. Cada enlace tiene asociada la probabilidad de que esa sea la siguiente palabra. Además de palabras hay que tener en cuenta dos nodos especiales: «inicio de frase» y «fin de frase». El primer nodo es por el que se empieza para formar una frase y al llegar al segundo se termina la frase.

La forma de usar este grafo es sencilla. Se parte del nodo «inicio de frase». El siguiente nodo se elige al azar, cada nodo tiene una probabilidad distinta de ser elegido. Una vez un nodo es elegido se repite la operación en ese nodo hasta llegar al nodo de «final de la frase».

Generar estos grafos a mano seria demasiado trabajo, por eso estos grafos se crean fácilmente a partir de textos. Tan sencillo como recorrer los textos e ir contando para cada palabra cual es la siguiente. Por ejemplo para la palabra «perro» tomando como base del aprendizaje las siguientes frases:

  • El perro ladró a la bicicleta.
  • El perro lamió la mano de su dueño
  • El perro olisqueó la comida antes de comerla
  • El perro olisqueó la prenda antes de seguir el rastro
  • Como el perro y el gato

El resultado serían:

GrafoPerro

Olisqueó tiene el doble de probabilidades de ser elegida como siguiente palabra que las demás.

El problema que vemos aquí es con las palabras muy habituales como «y». Detrás de «y» puede ir cualquier cosa que no tenga ninguna relación con las palabras anteriores de la frase. ¿Como evitamos eso?. En lugar de generar el grafo de una sola palabra lo generamos de dos o tres. En nuestro caso si lo generamos de dos palabras quedaría así:

El problema de usar más de una palabra es que el tamaño de los textos de aprendizaje ha de ser grande o se repetirán siempre las mismas frases. Vamos a añadir una frase más para que no todas empiecen por «el perro»

  • Al perro le gusta jugar con la pelota

El resultado

 

GrafoPerron

Con más palabras de profundidad las frases tienen más sentido, pero su variedad se resiente. Al final hay que buscar un equilibrio.

Este sistema sirve para generar frases que a veces tienen sentido o que por lo menos dan la sensación de que quieren decir algo. Es importante buscar textos similares para generar grafos que mantengan el sentido. Si por ejemplo mezclamos textos de gatos, animales, con gatos, herramienta, puede ser divertido el resultado pero difícilmente tendrá sentido. Al final el resultado de estas herramientas es más artístico que practico y el texto generado difícilmente tendrá sentido más allá de una frase, aunque si los textos están bien elegidos tendrás la sensación de que quiere decir «algo».

Un ejemplo muy sencillo de usar es esta aplicación cuyo uso es muy sencillo, basta con preparar los textos de ejemplo y pasarlos al generador:

python markov.py gen <name> <count>

  • Name es el nombre del fichero que contiene esos textos
  • Count es el nivel de profundidad de los nodos (el numero de palabras que tiene en cuenta)

Una de la ventajas de este sistema es que solo depende del idioma de los textos usado como fuente, el algoritmo funciona igual en todos los idiomas.

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

Chispas – Introducción

Antes de explicar qué es chispas voy a tratar de presentar que problema trata de solucionar chispas. Los sistemas «inteligentes» actuales tienen necesidad de elementos externos a tu propia infraestructura. Si compras una bombilla inteligente necesita estar conectada a Internet. Nuestra bombilla inteligente realmente se vuelve bastante tonta si no tiene un «cerebro» que le diga que hacer. ¿Por qué tiene que estar ese cerebro en la nube?. ¿Por qué tengo que depender de una empresa externa para que funcionen las luces de mi casa?. Puede parecer una tontería pero si la compañía cierra o es comprada por otra, como paso hace poco con Revolv, comprada por Nest (la propia Nest había sido comprada por Google) y que dejo tirados a un montón de usuarios de sus productos. ¿Como puede ser que cuando compro algo ese algo no sea mio?. Bueno, es mio como pisapapeles porque gran parte de sus funcionalidades se pierden sin los servicios que le dota una empresa que es la primera interesada en que les compre un pisapapeles nuevo. No soy de naturaleza desconfiada pero me suena sospechoso. Actualmente cualquier electrodoméstico puede durar fácilmente una década. ¿Va a mantener una empresa tanto tiempo los servicios que dotan de inteligencia a tu electrodoméstico?. Ojo, que mantener no es solo tener los servicios funcionando es también ir actualizando y corrigiendo los fallos y errores de seguridad que vayan apareciendo. Todo eso tiene un coste. ¿Van a seguir gastando dinero en un producto que hace tiempo que ya no venden y no les da actualmente ningún beneficio?. ¿Estamos seguro de que van a darnos la mejor solución o solo la más barata?. De hecho ya hay casos de productos con brechas grandes en su seguridad que al no poder, o no querer,  actualizar su firmware no queda más remedio que tirarlos a la basura.

Al problema de depender de empresas externas se suma que a su vez estas contratan los servicios de otras empresas y al final no sabes quién tiene acceso a tus datos. Y no es que estas empresas tengan que tener oscuros intereses en tus datos, pero un fallo de seguridad en cualquiera de ellas puede poner en peligro tu privacidad.

A todo esto hay que añadirle que muchos productos tienen incompatibilidades entre ellos. A veces por motivos técnicos, otras por motivos económicos para encerrarte en su «ecosistema». Y ya no hablo de integrar tus propios desarrollos que muchas veces todo son problemas.

amablemente le dices a tu teléfono «teléfono enciende la luz» y ocurre lo siguiente:

  • Le dices a tu móvil «teléfono enciende la luz»
  • Tu móvil graba tu voz y lo envía a unos servidores en algun otro lugar del mundo
  • Allí procesan tu voz, extraen el texto y lo envían de vuelta a tu móvil
  • Tu móvil procesa el texto y le pasa a la aplicación correspondiente
  • La aplicación le envía la petición al servidor de la empresa que te vendió la bombilla
  • El servidor procesa la petición y le envía a tu bombilla la orden de encenderse
  • Tu bombilla , conectada por WiFi a la red de tu casa, recibe la orden de encenderse
  • Tu bombilla se enciende

Todo ello para sustituir a un trozo de plástico y metal que cierra o abre un circuito según en que posición lo pongas y que a una mala puedes montar con un alambre y un clavo.

¿Chispas va ser la solución de todo este gran problema?. La verdad  es que no. Chispas pretende ser el intento de crear un sistema que facilite la creación de tus propios agentes de software inteligentes. La idea es que funcione en sistemas muy sencillos (y baratos) de tal forma que sea fácil montarte tu . De hecho mi plan es que funcione sobre una raspberry pi, arduino y el navegador de cualquier dispositivo (tablet, movil, PC) que soporten HTML5

Hasta ahora he dicho tres palabras mágicas que suena mucho pero no he explicado en que consisten. Como una imagen vale más que mil palabras la idea es esta:

Esquema0

 

Bueno, quizás lo que valga más que mil palabras sea una buena imagen y no esta. Pero como mis habilidades no dan para más tendremos que apañarnos con lo que hay. El dibujo explica que chispas se compone de:

  • Un servidor web, en este caso una Raspberry Pi, aunque puede ser cualquier ordenador. El único requisito es que pueda servir páginas sobre HTTPS. A su vez puede tener otras funciones como servidor de archivos o base de conocimiento (entendiendo como tal cualquier servicio que aporte información a nuestros agentes)
  • Uno o varios dispositivo con navegador web. El agente web de chispas actúa como intermediario entre el usuario y las funciones de chispas. Al ser una aplicación web no requiere instalación, solo conectarse con el navegador.
  • Chispas firmware funciona sobre uno o varios arduinos. Cada arduino se comporta a su vez como un agente independiente y tiene asociado un servidor web muy sencillo por lo que desde un navegador podrias conectarte directamente al arduino y controlarlo desde la interface que sirva el propio arduino. Para que esto funcione el arduino debe de ser capaz de conectarse a la red ya sea usando una shield, un desarrollo propio, o alguna placa que incorpore la conexión a ethernet

Cada uno de estas partes es independiente de las demás y trabaja de forma colaborativa entre ellas. si por ejemplo fallara la raspberry los arduinos seguirían funcionando y el agente web seguiría pudiendo interactuar con ellos.

Todo funciona sobre la red local con intercambio de peticiones HTTP. Es una arquitectura simple y que hace muy sencillo poder introducir componentes propios a cualquier nivel. Nada es propietario y cualquier parte es prescindible o modificable. No se depende de servicios externos pero eso no te impide conectar a Chispas a los mismos. Por ejemplo si queremos que reaccione a nuestro email nada nos impide poner en la raspberry código que lo haga y enviar esa información a los agentes.

En resumen, sencillo, barato, abierto y extensible.