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

Como saber si un elemento HTML se ve en pantalla en una web con scroll. IntersectionObserver

Todos los que llevamos un tiempo programando webs nos hemos tenido que enfrentar al scroll. ¿Qué vel el usuario en su pantalla? ¿Por que punto del div (párrafo, imagen,…) está?. Muchas veces la solución es un enrevesado conjunto de cálculos. Por suerte los navegadores han implementado un solución en el javascripr que permite hacerlo fácilmente: IntersectionObserver

Explicado de forma muy resumida, IntersectionObserver invoca a una función cuando el elemento html que le indiquemos se muestre en la pantalla en distintos porcentajes.

Primero veamos como se crea un IntersectionObserver:

let observer = new IntersectionObserver(handleIntersect, options);
observer.observe(element);

handleIntersect es la función a la que llamara el observer. Se le pasa un objeto entries que es un array de objetos entry correspondiente a vrios eventos de intersección asociados (por lo general es solo uno). Cada uno de los objetos entry tiene las siguientes propiedades:

entry.boundingClientRect
entry.intersectionRatio
entry.intersectionRect
entry.isIntersecting
entry.rootBounds
entry.target
entry.time

option es un objeto con tres posibles campos:

  • root: Indica que elemento que es usado como viewport para comprobar la visibilidad de nuestro elemento. Debe ser ancestro del nuestro. Por defecto es el viewport del navegador.
  • rootMargin: Delimita los margenes del elemento root que se ha de tener en cuanta al calcular la intersecciones. Por defecto son cero.
  • threshold: Es un número o un array que indican a que porcentaje de visibilidad se llama a la función asociada al observer. Su valor debe estar definido entre 0 (0%) y 1 (100%)

element es el elemento que el observer «estará vigilando»

Veamos un ejemplo completo:

<html>
<style>
    p {
        font-size: 120px;
        border: 2px solid black;    
    }
</style>

<body>
    <p id="p0">0</p>
    <p id="p1">1</p>
    <p id="p2">2</p>
    <p id="p3">3</p>
    <p id="p4">4</p>
    <p id="p5">5</p>
    <p id="p6">6</p>
    <p id="p7">7</p>
    <p id="p8">8</p>
    <p id="p9">9</p>    
</body>

<script>
    let element = document.getElementById("p5");

    let options = {
        root: null, //document.getElementById()
        rootMargin: '0px',
        threshold: [0, 0.5, 1.0]
    }

    let observer = new IntersectionObserver(handleIntersect, options);
    observer.observe(element);

    function handleIntersect(entries){
        entries.forEach(entry => {
            console.log("Elemento con id "+entry.target.id+"se muestra en pantalla en un porcentaje de: "+(entry.intersectionRatio*100));
        });
    }
</script>
</html>

Primeros pasos SDR. ¿Qué puedo hacer?

Acabas de comprar tu primer equipo de SDR que posiblemente sea un decodificador de TDT (DVB-T) con un chip RTL2832 o similar conectado por USB. ¿Ahora que?

Vamos a revisar rápidamente que herramientas hay que aprender a usar con tu equipo SDR.

Herramientas básicas

Lo primero es instalar las herramientas básicas. Para ello instalamos el paquete rtl-sdr.

Por ejemplo en mi distribución basada en Ubuntu:

 sudo apt-get install rtl-sdr

Con este paquete se instalan gran parte de las herramientas que vamos a usar en este texto.

Comprobar que todo funciona

Lo primero es comprobar si esta correctamente conectado y las capacidades de tu dispositivo. Para ello podemos usar:

rtl_test

La salida de la aplicación te muestra los datos de los sintonizadores encontrados.

Explorar el espectro de radio

Para ello puedes usar la aplicación gqrx que te permite explorar todo el espectro de radio.

Lo primero que puedes hacer es buscar una lista de emisoras de radio, sintonizar su frecuencia y probar a escucharlas. Puedes ver cómo afecta cambiar el ancho de banda o el modo de decodificación. Luego con cierta práctica puedes ir recorriendo el espectro a ver qué «escuchas». Por ejemplo puedes buscar en que frecuencia trabajan algunos equipos inalámbricos y analógicos para ver si alguien cerca usa alguno.

Cuando tengas más soltura y conocimiento del tema gqrx puede quedarse corto para ti y puede usar algun software con más opciones como SDR++

Otra herramienta imprescindible que hay que saber usar es rtl_sdr que te permite capturar la señal y guardarla en un fichero. Muchas herramientas trabajan con estas capturas.

rtl_sdr -f frecuencia -g ganancia -s samplerate -n numero_de_muestras archivo

También hay que saber usar rt_power que captura el espectro de la señal.

Vamos a «cazar» aviones. ADS-B

Otra cosa que podemos hacer es «espiar» que aviones pasan por encima nuestro. Para ellos podemos escuchar la señales del sistema ADS-B que los aviones emiten. Para leer estos datos puedes usar el comando:

rtl_adsb

Así lo único que consigues es montón de caracteres alfanuméricos con el parámetro -V la información se muestra de una forma más amigable

rtl_adsb -V

Hay que decir que no solo los aviones emiten este tipo de señales,

El resultado es algo más entendible aunque es difícil saber que significa. Aquí puedes tener una pista de que significa cada cosa. Haciendo un pequeño resumen.

El campo Type Code indica el tipo de mensaje:

Type CodeTipo de mensjae
1-4Identificación de la aeronave
5-8Posición
9-18Altitud (altimetro)
19Velocidad
20-22Altitud (GNSS)
23-27Reservado
28Estado de la aeronave
29Información de estado y estado de destino
31Estado operacional

El campo ICAO Address puede ayudarnos a identificar el avión y por tanto el vuelo. Para ello s epeuden suar webs como flightradar24.

Si lo que quiere es tener la información más clara puedes usar dump1090 (o dump109-mutability en el caso de mi distribución de Linux). Basta con lanzar lo con la opción –interactive y procesara esos datos por ti y te los mostrará de un forma más sencilla de entender.

dump1090 --interactive
dump1090-mutability --interactive

Es posible que te ocurra que rtl_adsb muestre un montón de resultados pero dump1090 no muestre ningún avión, se puede deber a estaciones emisoras en tierra. A menos que vivas cerca de un aeropuerto de una gran ciudad hay que tener paciencia para captar los datos de un avión.

Internet of Thing

Pasemos a capturar y decodificar mensajes de IoT. Para ellos puedes probar con el programa rtl_433.

rtl_433

Los datos son devueltos de una manera muy intuitiva donde cada paquete es decodificado y mostrado en pantalla.

Por defecto escucha la frecuencia 433 MHz pero usando el parámetro -f puedes probar con diferentes frecuencias a ver qué detectas, para probar puedes empezar con 315 MHz, 345 MHz, 868 MHz o 915 MHz. Por ejemplo:

rtl_433 -f 868M

Si deseas volcar los datos a un fichero con el parámetro -F puedes configurar que formato usar de la lista: kv|, json, csv, mqtt, |influx, syslog, |null.

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.