Transformar un valor de una escala a otra en Arduino. Map

Hay veces que tenemos un valor expresado en una escala y queremos pasarla a otra. Por ejemplo uno de los casos más habituales es el calcular un porcentaje. Que pasamos un número expresado en una escala a otra que va de 0 a 100. En Arduino para pasar un valor de un rango o escala [in_min, in_max] a otro [out_min, out_max] podemos usar la función map.

long map(long x, long in_min, long in_max, long out_min, long out_max)

La función map toma 5 parámetros:

  • x: Valor a transformar
  • in_min: Valor más bajo de la escala origen
  • in_max: Valor más alto de la escala origen
  • out_min: Valor más bajo de la escala destino
  • out_max: Valor más alto de la escala destino.

La función tiene la siguiente implementación interna:

long map(long x, long in_min, long in_max, long out_min, long out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}

Os habréis dado cuenta de que trabajo con long ¿Qué pasa si queremos otro tipo de datos?. Muy fácil, nos hacemos nosotros mismos la función:

float floatMap(float x, float in_min, float in_max, float out_min, float out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}


double doubleMap(double x, double in_min, double in_max, double out_min, double out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}

Optimizar la función map

Ya que nos hemos puesto a crear nuestras funciones veamos como podemos optimizar los cálculos, si siempre tenemos que usar la función map con los mismos rango de números podemos crear nuestra versión personalizarla para que se ejecute más rápido. Es tan sencillo como sustituir los valores min y max de cada rango y simplificar. Veamoslo con un ejemplo, vamos a crearnos nuestro propio map que transforme de la escala de 0 a 1023 a otra escala de 0 a 255.

in: 0..1023
out: 0..255
int_min = 0
int_max = 1023
out_min = 0
out_max = 255

Reemplazamos:

long customMap(long x) { 
    return ((x - 0) * (255 - 0) / (1023 - 0)) + 0; 
}

Simplificamos donde podamos:

long customMap(long x) { 
    return (x * 255) / 1023; 
}

Ahora tenemos nuestra función personalizada que realiza solo dos operaciones. Como no es necesario pesarle los rangos usa solo un parámetro.

Lanzar tareas en una web cuando el navegador está desocupado. RequestIdleCallback

Un problema típico cuando desarrollas webs con mucho javascript es tener que ejecutar ciertas funciones (precarga, envío de estadísticas, actualizar los datos almacenados en local,…) pero que esta ejecución ocurra cuando la carga de la web haya terminado. Y con carga no me refiero únicamente a que haya terminado de cargar el documentos, si no de inicia izar todas las librerías para no afectar al rendimiento de la web. Para ello existe una solución en los navegadores: RequestIdleCallback

RequestIdleCallback lo que hace es encolar las funciones que se le pasan y llamarlas cuando el el navegador tenga poca carga de trabajo, así se mejora el rendimiento de la web y la experiencia de usuario. Su uso es muy sencillo:

requestIdleCallback(nonEssentialFunction);

Donde nonEssentialFunction es la función a la que se llamará cuando el navegador quede inactivo. La función recibe un parámetro un objeto deadline que tiene dos propiedades que podemos usar para planificar la ejecución de nuestro código:

  • timeRemaining(), una función que devuelve cuanto tiempo tenemos para ejecutar nuestro código.
  • didTimeout, que indica si el tiempo fijado como limite para ejecutar nuestra función (más adelante lo veremos) ha expirado. Si su valor es true, timeRemaining() devolverá 0.

Es decir que consultando deadline.timeRemaining() podemos saber si tenemos tiempo suficiente para ejecutar nuestro código. Hay que decir que es un valor orientativo y que si realmente nuestro código tarda más tiempo en terminar no pasara nada.

Veamos el ejemplo más simple, ejecutar una solo función:

function nonEssentialFunction(deadline) {
  task();
}

Si tenemos que realizar varias tareas podemos ir haciéndolas de una en una hasta que se termine el tiempo. na vez terminado el tiempo no se han acabado todas las tareas se puede volver a llamar a requestIdleCallback:

function nonEssentialFunction(deadline) {
    while (deadline.timeRemaining() > 0){ //mientras quede tiempo
        task(n); //ejecutar tarea n
        n--;
    }

    if(n > 0) { //si aun quedan tareas se encola la función otra vez
        requestIdleCallback(nonEssentialFunction);
    }
}

Veamos un ejemplo completo:

<html>
<script>
    function nonEssentialFunction(deadline) {
        while (deadline.timeRemaining() > 0)
            console.log(deadline.timeRemaining());
    }

    //Se prepara la función cuando este libre el procesador
    requestIdleCallback(nonEssentialFunction);
</script>

<body>
</body>

<script>
    //esto se ejecuta antes que nonEssentialFunction
    for(let i = 0; i < 100; i++){
        console.log(i);
    }    
</script>
</html>

Poner un limite de tiempo

Puede ser que no queramos que la función que queremos llamar se retrase demasiado, para ellos podemos pasar otro parámetro que establece el tiempo de expiación, pasado ese tiempo se lanza la ejecución de la función:

requestIdleCallback(nonEssentialFunction, { timeout: 2000 });

El tiempo viene expresado en milisegundos.

Cuando una función es ejecuta porque expira el tiempo fijado en timeout se puede ver en que deadline.didTimeout es true.

function nonEssentialFunction(deadline) {
    while (deadline.timeRemaining() > 0 || deadline.didTimeout )
        console.log(deadline.timeRemaining());
}


requestIdleCallback(nonEssentialFunction, {timeout, 3000});

Comprobar la aleatoriedad. Test de rachas.

Uno de los problemas de tener un generador de números que parecen aleatorios es estar seguro de si lo son. Hay que tener en cuenta que hablamos de números aleatorios justos, donde todos sean igual de probables, en este caso de números binarios que con un 50% salga 0 o 1 (existen formas de asegurarse de que una fuente aleatoria es justa). Vamos a ver el test de rachas para validar la aleatoriedad.

Test de rachas

En el caso de números binarios este test es muy fácil de entender, contamos el número de rachas que hay en los datos. ¿Qués un racha? Cada vez que un valor es distinto que el valor anterior. Por ejemplo:

00110101100111100

Si lo separamos en rachas:

00 – 11 – 0 – 1 – 0 – 11 – 00 – 1111 – 00

Ahora contamos las rachas, los ceros y los unos. En este caso hay 9 rachas, 8 ceros y 9 unos.

Si el número de rachas es R, el número de ceros es n0, el de uno es n1 y n = n0+n1.

media => u = (2*n0 *n1 / n) + 1

varianza => var = 2*n0*n1*(2*n0*n1-n) / n² * (n-1)

desviación típica => des = sqrt(var)

Z = R + c – u / des

Si R > u → c = -0,5

Si R < u → c = 0,5

El resultado es el Z score. Podemos usar tablas para saber su valor pero si queremos tener una buena aproximación con una seguridad del 95% de que es una distribución aleatoria no puede ser mayor de 1.6 ni menor de -1.6.

R = 9

n0 = 8

n1 = 9

n = 17

u = (298 / 17) +1 = 9,47

var = 298 * (298-17) / 17² * (17-1) = 18,10

des = 4,25

Z = 9 – 0,5 – 9,47 / 4,25 = -0,22

-0,22 esta entre 1.6 y -1.6 con lo cual lo consideraríamos como aleatorio.

Creatividad artificial. Generar imágenes para microcuentos con VQGAN+CLIP

Llevo un tiempo probando VQGAN+CLIP para generar imágenes a partir de texto y quería hacer algo con él, pero no sabia el que. Hasta que recordé que hace años tenia un twitter llamado tweetcuento donde publicaba unos microcuentos que escribía. Sin entrar en detalles sobre mi calidad como cuentista me pareció que podría ser una buena idea usarlo de base para un experimento de creación de imágenes

VQGAN+CLIP es la unión de dos sistemas. VQGAN y CLIP . CLIP funciona como un puente entre las imágenes y el texto. Explicado pronto y mal, CLIP generaría el mismo valor (o al menos uno muy cercano) para una foto de una gafas que para la palabra “gafas”. Ahí radica la clave de este sistema de generación de imágenes, se le pasa un texto a CLIP y luego una imagen generada por VQGAN. Y trata de que el valor del texto y la imagen se aproximen. VQGAN usa este valor para mejorar su resultado. Si queréis una explicación más exhaustiva DotCSV tiene un video estupendo sobre el tema.

Todo este proceso lo he realizado a partir de un notebook de google colab creado por Katherine Crowson . Sin embargo para usarlo con mis cuentos tenia que realizar varios pasos.

CLIP solo funciona en inglés así que toca traducir los microcuentos. Podría hacerlo yo pero me parece más interesante que lo haga un traductor automático (y es posible que lo haga mejor que yo). He recurrido al traductor de Google, pero no uso directamente sus resultados. Antes los reviso y solo los modifico si hay alguna palabra cuya traducción es errónea ya que podría influir en el resultado. En el resto delos casos dejo la traducción como esta. En las pruebas realizadas hasta ahora no he necesitado modificar la mayoría de los resultados.

Luego adapto el texto para pasarlo a CLIP. Elimino los símbolos que no sean letras o números. Cada frase separada por un punto la separo, los mismo hago con las conversaciones.

- Quiero ese globo - la niña señaló un pobre globo que apenas se elevaba del suelo
- Los hay más bonitos
- Si, pero este sé que no me dejará

Se convierte en

["Quiero ese globo", "la niña señaló un pobre globo que apenas se elevaba del suelo", "Los hay más bonitos", "Si pero este sé que no me dejará"]

En realidad el texto estaría en inglés pero el ejemplo se entiende mejor así.

Otra condición que me he autoimpuesto es que se generan solo 3 imágenes por cada cuento. Elegiré la que me parezca más adecuada, si dudo entre varias imágenes publicaré todas entre las que esté dudando.

Además esperare un par de días para evitar sesgos. He notado que cuando estas mirando como el sistema produce sus imágenes te parecen mucho mas sorprendentes unos que días después.

Las imágenes obtenidas son tal y como las genera VQGAN+CLIP . Muchas de ellas serian fácilmente mejorables con algún pequeño retoque.

Por último y puesto que no podían faltar los NFTs cada imagen es convertida en un NFT y ofrecida en una galería en Opensea. No creo que nadie vaya a comprarlas pero quería aprender el proceso.

Veamos un ejemplo de todo este proceso, partiendo del cuento:

"Las máquinas se rebelaron, ganaron la guerra y esclavizaron a la humanidad. Se convirtieron en los amos del mundo. Esta situación se prolongó durante tres años hasta que las máquinas perdieron ante su mayor enemigo, la obsolescencia programada."

La imagen obtenido ha sido:

Para ver mas ejemplos los iré publicando poco a poco en tweetcuento

Password de un solo uso (OTP) en Arduino (HMAC, HTOP, TOTP)

Los password de un solo uso también llamados OTP (One Time Password) son passwords que solo se pueden usar una vez o durante un periodo de tiempo breve. Es una buena medida de seguridad, aunque estas contraseñas sean robadas o interceptadas se reduce el tiempo que el atacante puede acceder al sistema. Uno de sus usos es como segundo factor de autenticación. Casi todos habremos tenido que usar alguna vez códigos que nos envían al móvil o que genera algún aplicación y cuyo tiempo de vida es breve. Eso es un OTP.

Cuando hay un canal seguro para notificar el OTP, por ejemplo un mensaje por SMS. Es sencillo crear un OTP, se genera un código aleatorio asociado a ese usuario y listo.

Pero no siempre hay un canal seguro. En ese caso la solución es algo más complicada. Necesitamos tener dos programas que generen exactamente la misma contraseña en el mismo momento. Para estos sistemas necesitaremos dos cosas.

  • Un secreto o clave, que es compartido por ambos sistemas y debe de permanecer en secreto para que sea seguro. La llamaremos k
  • Una secuencia o contador, este dato puede ser publico sin ningún problema (en teoría) sirve para que ambos programas generadores creen la misma contraseña. Lo llamaremos c

Con random

Una implementación muy sencilla y de muy baja seguridad (suficiente para proyectos caseros) es usar el generado de números pseudoaleatorios de Arduino. En este caso el secreto es la semilla con la que se inicializa el generador. En este caso la secuencia o contador no es explicito, no se le pasa es implícito al numero de veces que se ha llamado a la función random. Lo que añade la dificultad añadida de tener ambas secuencias sincronizadas.

long randNumber;
void setup() {
  randomSeed(k);
}
void calculateOTP() {
  return random(10000, 99999);//contraseña de 5 digitos
}

Otro problema es que cada vez que se reinicia perdemos el punto donde estábamos de la secuencia y esta vuelve a empezar. Seria necesario guardar en la EEPROM el número de veces que hemos generado un OTP y luego llamar a la función ese numero de veces para resincronizar el estado del generador de números pseudoaletaorios.

Con una función hash

Otra táctica parecida es usar una función de hash. Para calcular la primera contraseña se llama a la función de hash con el secreto k, luego se usa el password anteriror, añadiendole el secreto k para calcular el siguiente:

k0 = H(k)

k1 = H(k0+k)

k2 = H(k1+k)

k3 = H(k2+k)

….

kn = H(kn-1+k)

La ventaja de este sistema es que basta con guardar en la EEPROM el último password generado para mantener la sincronización.

HTOP

Estos dos sistemas nos sirven para desarrollos caseros que no requieren demasiada seguridad para hacer una implementación segura podemos usar HMAC (hash-based message authentication code). HMAC usa una función de hash, un secreto o clave y un mensaje, en este caso el mensaje será el contador. A esta implementación se le conoce como HTOP (HMAC-based one-time password).

Empecemos viendo como funciona HMAC

HTOP(k, c) = HMAC(k, c) = H( (k ^ opad) || H(k ^ ipad) || c )

  • H es la función e hash
  • k es la clave secreta
  • c es el contador
  • opad es un bloque formado por el valor 0x5c repetido
  • opad es un bloque formado por el valor 0x36 repetido
  • || es la operación OR
  • ^ es la operación XOR

En este caso no necesitamos tener el contador sincronizado. El contador se puede pasar en la petición. Supongamos que el dispositivo B intenta contactar con el dispositivo A, el proceso puede ocurrir de dos maneras:

  • B usa su contador interno y la pasa el password (TokenB ) con el contador usado para calcularlo. A calcula el password con el contador que le pasa B y verifica si coinciden
  • B le pide a A un contador y A le pasa el contador con el que B tiene que generar el password (tokenB) y se lo devuelve a A que verifica si coinciden

El segundo caso es más seguro ya que evitamos que un atacante

TOTP

Lo ideal seria que ninguno tuviera que intercambiar el contador, que ambos tuvieran un contador sincronizado. Una opción seria usar la hora actual. O un timestamp que exprese la hora actual como milisegundos transcurridos desde el 1 de enero de 1970 a las 00:00:000. Es decir que TOTP (Time-based One-time Password) es HMAC usando como mensaje el tiempo o lo que es lo mismo usar HTOP usando como contador el tiempo.

El único problema aquí es que cada milisegundo la contraseña cambia y puede ser muy poco tiempo para realizar el intercambio de password. La solución es dejar una ventana de tiempo durante la cual el password es válido. Si usamos t para indicar el tiempo en milisegundo y vt el tiempo (en milisegundos) durante el cual el password es válido.

c = Trunc(t/vt)

TOTP(k, c) = HMAC(k, c)

Así nos ahorramos la parte de tener que intercambiar el contador entre dispositivos. Ambos usan la hora actual con una ventana de tiempo vt.

El problema es que Arduino no tiene un RTC (real time clock) por lo que no tiene forma de saber directamente qué hora es, necesita un elemento externo que le informe de ello.

“TOTP” sin RTC

Hay una forma un poco tramposa de implementar TOTP sin usar un RTC. En este caso partimos de HTOP, usaremos la segunda forma de HTOP en la que A le pasa el contador a B. El funcionamiento es exactamente igual que antes solo que tras un periodo de tiempo t la variable contadorA se incrementa automáticamente dejando a B con un OTP que ya no es valido.

No es un sistema tan cómodo como tener ambos sistemas sincronizados por su reloj. Ademas de que si varios dispositivos quieren conectarse al dispositivo A puede ser complicado de gestionar y necesitarías un contador y un secreto para cada uno.

¿Cual elegir?

Las soluciones basadas en HMAC son las más seguras pero también las más exigentes en tiempo y memoria. Y aunque TOTP es la segura y cómoda implica tener un RTC y que todos los dispositivos lo tengan sincronizados. Cada uno tiene que elegir el método según el balance de seguridad y coste.

Código

Para implementar todo esto podemos usar la librería Cryptosuite para Arduino. Permite usar la funciones hash SHA-1 y SHA-256.

Una vez copiada a nuestro directorio de librerías de Arduino puede usarse añadiendo el include correspondiente

#include "sha1.h"
#include "sha256.h"

Un ejemplo de uso de HMAC es:

 uint8_t *hash;
 Sha256.initHmac(key,keyLength); //secreto y su longitud
 Sha256.print(counter); //contador
 hash = Sha256.resultHmac();

Regresión lineal con incertidumbre en Arduino

Vamos a empezar este texto develando el truco que usaremos para representar incertidumbre con la regresión lineal para Arduino y que se basa en emplear la versión con pesos del algoritmo de regresión lineal. La incertidumbre estará representada como valores con una variación de pesos según la certeza que tengamos de su valor. Usaremos el peso como porcentaje de certeza de ese dato

Esta no es la mejor ni la única manera de hacerlo. No hay que olvidar que aquí se trata de hacerlo en algo tan limitado en memoria y potencia como pude ser un Arduino UNO.

Usaremos la librería regressino, en concreto su librería para regresión lineal:

#include <LinearRegression.h>

LinearRegression lr = LinearRegression();

Una forma de incertidumbre es cuando directamente tenemos valores de los que “nos fiamos” menos que de otros. Por ejemplo, porque vienen de dos fuentes distintas. En este caso los datos menos fiables tendrán que tener un peso más bajo que los más fiables para que su influencia sobre el resultado final sea menor.

//datos fuente no fiable
lr.learn(1, 3, 0.5);
lr.learn(2, 5, 0.5);
lr.learn(3, 6, 0.5);

//datos fuente fiable
lr.learn(2, 4, 1);
lr.learn(4, 5, 1);
lr.learn(5, 6, 1);

Pero hay otro caso de incertidumbre, cuando no conocemos el valor del dato con seguridad, lo que conocemos son los valores entre los que está comprendido. Generalmente tenemos dos valores, un mínimo y un máximo o tres valores con uno más probable y un mínimo y un máximo (a veces representados como errores) entre los que ese valor puede variar. En este caso tenemos que definir cómo se distribuye el peso (probabilidad) entre estos valores. Hay que recordar que la suma total de los pesos tiene que ser igual a 1.

Una vez definida la forma en que se distribuye la probabilidad hay que descuartizarla en puntos. La idea es que esto funcione en un Arduino UNO y no podemos trabajar directamente con funciones de probabilidad.

Supongamos que para x = 10 sabemos que el valor de y está comprendido entre 2 y 3. Ahora hay que saber cómo está distribuida la probabilidad entre esos dos valores. Veamos algunas posibilidades:

  • Toda la probabilidad se concentra en cada uno de esos valores por lo tanto el 2 tendría un 50% de certeza y el 3 otro 50%. O lo que es lo mismo un peso de 0.5
lr.learn(10, 2, 0.5);
lr.learn(10, 3, 0.5);
  • La probabilidad se distribuye de forma uniforme por todo el espacio entre esos dos puntos. Para representarlo tómanos varios puntos entre 2 y 3 y les asignamos a todos la misma probabilidad.
lr.learn(10, 2, 0.2);
lr.learn(10, 2.25, 0.2);
lr.learn(10, 2.5, 0.2);
lr.learn(10, 2.75, 0.2);
lr.learn(10, 3, 0.2);
  • El punto central es mucho más probable que los extremos. Un ejemplo de como podemos hacerlo.
lr.learn(10, 2, 0.25);
lr.learn(10, 2.5, 0.5);
lr.learn(10, 3, 0.25);
  • Si por ejemplo queremos simular una distribución en campana de media u y con desviación estándar s
lr.learn(10, u, 0.682);
lr.learn(10, u-s, 0.136);
lr.learn(10, u-(2*s), 0.023);
lr.learn(10, u+s, 0.136);
lr.learn(10, u+(2*s), 0.023);

Regresión lineal con pesos en Arduino

Ya hemos visto varias formas de extender las capacidades de la regresión lineal en Arduino. En este caso vamos a asociar pesos a los valores para que no todos los casos aporten los mismo al resultado final. Pero qué significa “aportar” más al resultado, de forma gráfica podríamos imaginarnos que los puntos de mayor peso atraen más a la recta de la regresión lineal por lo que esta tiende a acercarse más a estos. La utilidad de este sistema es cuando tenemos resultados que por algún motivo valoramos más que otros.

Vamos a modelar el peso como un valor entre 0 y 1. De tal forma que el peso máximo corresponda con 1 y el mínimo con 0. A mayor peso más “aporta” ese valor al resultado. Entre dos valores uno con peso 1 y otro con peso 0.5 el primer cuenta el doble que el segundo. Cuando el peso es 1 no hay diferencia con la regresión lineal sin pesos. Y cuando el peso es cero el valor no va a aportar nada al resultado.

Partimos de la función de regresión lineal que ya vimos en otro post:

void LinearRegression::learn(double x, double y){
    n++;
    meanX = meanX + ((x-meanX)/n);
    meanX2 = meanX2 + (((x*x)-meanX2)/n);
    varX = meanX2 - (meanX*meanX);

    meanY = meanY + ((y-meanY)/n);
    meanY2 = meanY2 + (((y*y)-meanY2)/n);
    varY = meanY2 - (meanY*meanY);

    meanXY = meanXY + (((x*y)-meanXY)/n);

    covarXY = meanXY - (meanX*meanY);

    m = covarXY / varX;
    b = meanY-(m*meanX);
}

Como ya hemos visto en otro post vamos a usar “el truco” que consiste en transformar el valor de x e y que se le pasa a la función que calcula la regresión lineal (learn). Podríamos verlo como que el valor de x se usan para modificar el valor de meanX y meanX2 y el de y para meanY y meanY2. Entonces si w es el peso:

  • Si el peso es 1 x e y no cambian su valor
  • Si es 0 x e y no tienen que afectar a los valores de meanX y meanY para que pase eso el valor de x ha de ser igual que meanX y el de y igual a meanY.
  • Si el peso este entre 0 y 1 el valor ha de componerse con el valor de x e y y el valor de las medias de cada uno.

Para conseguir eso vamos a ponderar el valor de la x y el de meanX según w

x = x*w + meanX*(1-w);
y = y*w + meanY*(1-w);

Solo queda añadir un par de comprobaciones para evitar que el valor pueda ser mayor de 1 o menor de 0:

void LinearRegression::learn(double x, double y, double w){
    if(w >= 1) { 
        learn(double x, double y);
    } else if(w <= 0) {
        return;
    } 

    x = x*w + meanX*(1-w);
    y = y*w + meanY*(1-w);
    learn(double x, double y);
}

Todo esto se puede ver en la librería Regressino.

Por último comentar que hay dos posible optimizaciones que se podrían aplicar si fuera necesario y que nos permiten ahorrar algunos cálculos si hay muchos pesos cercanos a 0 o a 1:

  • Los pesos muy cercanos a 0 se ignoran
  • Los pesos muy cercanos a 1 se tratan como si no tuvieran peso


Hay una pequeña pérdida de precisión pero en el caso de tener muchos datos puede compensar al reducir el tiempo de cálculo.

Usar KDEConnect para crear una botonera y controlar acciones en el PC desde el móvil

Vamos a ver como usar KDEConnect para crear una botonera en el móvil para interactuar con el ordenador y las aplicaciones. En principio es fácil ya que KDEConnect da la opción de “ejecutar órdenes” en el ordenador desde el móvil, basta con dar de alta el comando en el ordenador y aparecerá un botón en la aplicación para móvil que permita lanzarlo. Para ello hay que ir a la configuración de KDEConnect y luego a la configuración del plugin “Ejecutar órdenes”. Ahí podemos dar de alta las nuevas órdenes indicando el texto que se mostrará en el botón en el móvil. Tenemos la limitación de que solo se puede introducir ordenes en una línea, si queremos lanzar varios comando uno tras otro tenemos dos opciones: crear un fichero de script y llamar a ese fichero, introducirlas en esa línea separada por “&&”.

Ejecutar comandos en segundo plano:

Es el caso más sencillo, para ello podemos usar la opción “Ejecutar órdenes” que hemos comentado antes. Los comando se lanzan en segundo plano.

Ejecutar acciones en programas:

En este caso queremos realizar acciones sobre aplicaciones. Lo que significa que no siempre tendremos un comando que lo haga. Si por ejemplo queremos que al pulsar sobre un comando de la pantalla de nuestro movil se cambie el pincel de nuestra herramienta de dibujo seŕa necesario simular pulsaciones de teclado. Para casos más complicados es posible que tengamos que simular clicks de ratón. Para ello en Linux tenemos la herramienta xdotool que permite simular el teclado, el ratón, actuar sobre las ventanas del sistema y el escritorio.

Supongamos que queremos tener un comando que cada vez que pulsemos su botón correspondiente en el móvil simule que se teclea la fecha:

xdotool type $(date +"%d/%m/%y %H:%M")

O que pulse control + s para guardar el archivo que este editando:

xdotool key ctrl+s

Abrir una aplicación

Tenemos dos opciones usar xdotool exec o directamente usar el comando que lanza el programa. Por ejemplo para lanzar VLC:

vlc

xdotool exec vlc

Control multimedia

No hay que hacer nada, KDEConnect ya tiene un plugin para controlar la reproducción multimedia del PC desde el móvil.

Teclado y ratón remotos

Tampoco hay que hacer nada, KDEConnect ya permite usar la pantalla del móvil como ratón y el teclado remotos

Mostrar la respuesta en el móvil

Es posible que queramos que el comando lanzado nos devuelva algún mensaje diciéndonos si ah terminado y ha sido con esxito.Ya hay un post sobre este tema, puedes leerlo aquí.

Como ejemplo vamos a usar un comando que nos permite ver cuanto espacio nos queda en el disco:

kdeconnect-cli -d $(kdeconnect-cli -l --id-only) --ping-msg "$(df -h)"

Iconos

Para que el resultado sea al más visual puedes añadir emojis como si fueran iconos. Puedes usar algún teclado de emojis web para copiarlos y pegarlos o instalar uno como emoji-keyboard. Puedes usar varios emojis y combinarlos con otros caracteres alfanuméricos.

El resultado

Ejemplo de configuración en el PC

Configuración de KDEConnect en el PC

Resultado en la pantalla del movil

Resultado en la pantalla del movil

Enviar datos desde el ordenador al móvil con KDEConnect

Vamos a ver como mandar desde un script datos al móvil usando la herramienta KDEconnect. Para ello teneos dos comandos: kdeconnect-cli -d <device-id> –ping-msg “mensaje”que envía una notificación al movil con el texto del “mensaje” y kdeconnect-cli -d <device-id> –share <ruta> que envía el fichero que este en la ruta indicada.

Obtener device-id

Para obtener el listado de ids de todos los dispositivos que han sido vinculados a tu ordenador basta con ejecutar el comando:

kdeconnect-cli -l

El resultado es un listado con el nombre del dispostivo, su device-id y el estado. Si se desea obtener solo el el id se puede usar el parámetro –id-only :

kdeconnect-cli -l --id-only

Si solo tenemos un dispositivo vinculado podemos usar este comando para evitarnos tener que apuntar el device-id, en lugar de poner el device-id podemos usar:

$(kdeconnect-cli -l --id-only)

Más adelante veremos su uso dentro de un comando.

Enviar una notificación

Supongamos que queremos volcar el resultado de un comando en una notificación, podemos usar:

kdeconnect-cli -d <device-id> --ping-msg "$(ls -al)"

Como ya hemos dicho si solo tienes un dispositivo vinculado puedes usar:

kdeconnect-cli -d $(kdeconnect-cli -l --id-only) --ping-msg "$(ls -al)"

Podemos mandar un fichero corto:

kdeconnect-cli -d $(kdeconnect-cli -l --id-only) --ping-msg "$(cat fichero.txt)"

Este sistema tiene la ventaja de que la notificación aparece de forma inmediata en el teléfono, pero presenta la desventaja de que no se pueden enviar grandes cantidades de texto porque no “caben” y se cortan.

Enviar un fichero

Para enviar un fichero basta con conocer su ruta y usar el siguiente comando:

kdeconnect-cli -d <device-id> --share <ruta>

Podemos usarlo para enviar la salida de un comando:

ls -al > out.txt && kdeconnect-cli -d <device-id> --share out.txt

Lo podemos combinar con el “truco” de antes del device-id:

ls -al > out.txt && kdeconnect-cli -d $(kdeconnect-cli -l --id-only) --share out.txt

En el móvil aparece una notificación de la descarga del fichero.

Memoization y recursividad

Ya hemos visto como usar la memoization para mejorar el rendimiento del código memorizando los resultados de las llamadas a funciones. sin embargo este sistema tiene un punto débil. Las funciones recursivas. Veamos un ejemplo con la función factorial:

function factorial(n){
    console.log("factorial "+n);
    if(n < 2){
        return 1;
    } else {
        return n*factorial(n-1);
    }
}

Ahora crearemos un objeto Memo donde almacenar las llamadas y los resultados:

let memof = new Memo(factorial);
memof.call(4);
factorial 4
factorial 3
factorial 2
factorial 1
24

memof.call(4);
24

memof.call(5);
factorial 5
factorial 4
factorial 3
factorial 2
factorial 1
120

Se puede ver que tras calcular el valor de factorial(4), cuando se llama a factorial(5) este vuelve a llamar a factorial con valores que factorial(4) ya ha llamado y que no han sido memorizadas. Esto se debe a que dentro de la función factorial se llama a factorial(n-1) en lugar de a memof.call(n-1) por lo que esa llamada no pasa por el proceso de memorización y no se guardan esos resultados. Esto le quita mucha eficacia a la memoization. Sin embargo hay un truco que no es muy elegante pero que puede ayudarnos.

Cuando declaras una función en javascript es como si declararas una variable y le asignaras una función:

function factorial(n){
...
}

Es lo mismo que:

let factorial = function(n){
...
}

Que pasaría si le asignáramos a la variable factorial otra función distinta, pues que el código que llama a factorial llamaría ahora a esa nueva función. Y si esa función llama a memof.call() las llamadas recursivas se memorizarían:

factorial = (n) => memof.call(n);
factorial(4);
factorial 4
factorial 3
factorial 2
factorial 1
24

factorial(4);
24

factorial(5);
factorial 5
120

Pero si la función factorial ya no apunta al código de factorial ¿Como es que sigue funcionando y calculando el valor del factorial?. La respuesta es que la función original sigue referenciada:

class Memo {

    constructor (func, keyFunc) {
        this.func = func;
        this.cache = {}; 
        this.calculateKey = keyFunc || this.calculateKey;
    }
....
}

El código anterior sigue siendo accesible desde la variable func de la clase memo.

Podéis encontrar todo el código y ejemplos en la librería memo.js