Watchdog en Arduino

Un watchdog es un sistema de seguridad que usan muchos microcontroladores y sistemas embebidos. Su funcionamiento es simple, cada vez que se reinicia el watchdog empieza una cuenta atrás hasta cero, si antes de llegar a cero el watchdog se reinicia la cuenta atrás vuelve a empezar. Si no se reinicia y el contador alcanza el valor 0 el sistema se reinicia automáticamente. Así se evita que se quede colgado.

Con el reinicio se pierden los datos que están en la memoria SRAM por lo que todo dato que queramos conservar ha de guardarse en la EEPROM.

Para usar el watchdog hemos de incluir la librería del fabricante del microcontrolador:

#include <avr/wdt.h>

Para deshabilitar el watchdog se puede usar la función:

wdt_disable();

Empezamos aprendiendo a deshabilitarlo porque es lo primero que hay que hacer para evitar problemas durante el inicio de la placa o puede interferir en el proceso de grabación de un nuevo código en la placa. Cuando se vuelca un nuevo código hay que dar tiempo para que se pueda volcar un nuevo programa o se perderá la posibilidad de grabar nuevos programas. La causa de esto es que cuando se empieza a subir un nuevo código desde el IDE se paraliza la ejecución del programa, por lo que no se reinicia el contador del watchdog si este llega a cero antes de que se termine de subir el código la placa se reinicia y la grabación se corta.

Para activar el watchdog y fijar cuanto tiempo tiene que esperar el watchdog antes de reiniciar la placa se usa la función:

wdt_enable(WDTO_1S);

El parámetro que recibe la función indica el tiempo que esperará el watchdog antes de reiniciar el dispositivo. No puede tomar cualquier valor, tiene que ser una de los siguientes valores predefinidos:

  • WDTO_15MS
  • WDTO_30MS
  • WDTO_60MS
  • WDTO_120MS
  • WDTO_250MS
  • WDTO_500MS
  • WDTO_1S
  • WDTO_2S
  • WDTO_4S
  • WDTO_8S

Para evitar que se reinicie la placa hemos de llamar a la siguiente función ante de que pase el tiempo indicado:

wdt_reset();

Tras llamarla la cuenta atrás comienza otra vez.

Un ejemplo de esqueleto de función seria la siguiente:

#include <avr/wdt.h>

setup(){
  wdt_disable();
  //el resto del setup
  delay(3000); //para evitar que no nos da problemas al cargar nuevos programas
  wdt_enable(WDTO_4S); //se activa el watchdog 
}

loop(){
 wdt_reset(); //reiniciar contador del watchdog
 //código del programa
}

Memoization con tiempo de vida

La memoization ya vimos lo que era y como usarla. Ahora vamos a añadir una característica más que puede resultar útil en algunos casos: “tiempo de vida”. Cada respuesta memorizada solo va a ser válida durante un periodo de tiempo. Pasado ese tiempo el resultado deja de ser válido y si se vuelve a llamar

Todo lo que veamos en este texto esta publicado en la librería memo.js

El primer cambio que vamos a realizar sobre lo que vimos en el anterior post de memoization es que tendremos dos variables para almacenar los datos, una con la cache de los parámetros y sus valores y otra con el momento en que se almacenó esa cache.

La forma de funcionar es la misma que la memoization normal solo que cuando se va recuperar un valor de la cache se comprueba cuanto tiempo hace que se almaceno y si ha transcurrido un tiempo mayor que el tiempo de vida fijado se llama a la función y se actualiza la cache con el valor que devuelva.

Veamos algo de código partiendo de la librería de memoization que creamos.

Lo primero es extender la clase original:

class MemoTime extends Memo {

}

Modificamos el constructor para que cree una variable (lifetime) donde guardar cuando fue la última vez que se actualizo esa clave. También se le tiene que pasar al constructor el tiempo de vida (timeOfLife)

 constructor (func, timeOfLife, keyFunc) {
    super(func, keyFunc);
    this.timeOfLife = timeOfLife || 1000;
    this.lifetime = {};        
 }

También hay que modificar la función que añade una clave – valor para que almacene el momento (new Date()) en que se guarda esa clave.

add(key, value){
    this.lifetime[key] = new Date();
    super.add(key, value);
}

Por último la función que verifica si esta en la cache para que responda ‘false’ en el caso de que este pero haya caducado:

 isInCache(key){
   if(key in this.lifetime) {
      if((new Date() - this.lifetime[key]) < this.timeOfLife){ //¿ha caducado?
         return true;
      }
   } else {
      return false;
   }
}

El resultado final es el siguiente:

class MemoTime extends Memo {
    constructor (func, timeOfLife, keyFunc) {
        super(func, keyFunc);
        this.timeOfLife = timeOfLife || 1000;
        this.lifetime = {};        
    }

    isInCache(key){
        if(key in this.lifetime) {
            if((new Date() - this.lifetime[key]) < this.timeOfLife){
                return true;
            }
        } else {
            return false;
        }
    }

    add(key, value){
        this.lifetime[key] = new Date();
        super.add(key, value);
    }
}

Puede parecer contradictorio tener un mecanismo para almacenar los resultados de una función para no tener que volver a llamarla y que caduquen. Esto es útil cuando el resultado de la función cambia con el tiempo o queremos evitar que a una función se le llame muchas veces seguidas.

Por ejemplo si tenemos un servidor que nos devuelve un dato y no queremos saturarlo de peticiones podemos limitar el número de las mismas usando está técnica. Así cada vez que se llame a la función se tendrá una respuesta pero como mucho se le llamara una vez por cada periodo indicado en el tiempo de vida.

Por último vamos a ver un ejemplo de la clase que hemos creado:

function sum(a,b){  
    console.log("calculating "+a+" + "+b);  
    return a+b;  
}  

let memoTime = new MemoTime(sum, 2000);

console.log(memoTime.call(4,5)); //call sum

console.log(memoTime.call(4,5)); //no call sum
setTimeout(memoTime.call(4,5),1000); //no call sum
setTimeout(memoTime.call(4,5),3000); //call sum

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

Memoization y persistencia

Ya hemos visto como crear nuestra propia librería de memoization. Pero sus beneficios duran solo mientras la aplicación está en memoria. Si por ejemplo recargamos la web donde la usamos perdemos todos los resultados memorizados. Para evitar eso podríamos exportar el mapa de clave valor que guarda los parámetros y su valor. Una vez exportado podemos persistirlo como queramos. En el servidor, en el almacenamiento local del navegador donde se quiera.

Todo lo que se comenta en este texto se puede encontrar en la librería memo.js

Para exportado vamos a convertirlo en una cadena de texto que contenga los datos en formato JSON. En nuestro caso los datos están guardados dentro de la variable cache, la cual convertiremos a cadena usando JSON.stringify()

    getCache(){
        return JSON.stringify(this.cache);
    }

Una vez tenemos la cadena de texto para poder persistirla es necesario poder hacer el paso contrario. Recuperar los datos de la cache partiendo de la cadena para ellos emplearemos JSON.parse()

    loadCache(cache){
        this.cache = JSON.parse(cache);
    }

Vamos a ver un ejemplo sacado de la librería memo.js de como usar estas dos funciones para “clonar” un objeto que guarda la memoization de la función sum:

<html>

<script src="memo.js"> </script>

<body>
Abre la consola!!!!
</body>

<script>
function sum(a,b){
    console.log("calculando "+a+" + "+b);
    return a+b;
}

let memo = new Memo(sum);

console.log(memo.call(1,2)); //invoca sum
console.log(memo.call(1,2)); //no invoca sum
console.log(memo.call(2,3)); //invoca sum
console.log(memo.call(2,1)); //invoca sum

let storeMemoCache = memo.getCache(); //exporta la cache a String

//si se desea persisitir la memoization basta con persistir storeMemoCache

let memo2 = new Memo(sum);
memo2.loadCache(storeMemoCache); //carga la cache
console.log(memo2.call(1,2)); //no invoca sum

</script>

</html>

Uso de memoization para mejorar el rendimiento

Memoization, o memoización en español, es una técnica que permite ahorrar tiempo y recursos en el caso de que tengamos funciones cuyo tiempo de ejecución sea muy largo y se le llame varias veces con los mismos parámetros.

La idea es muy sencilla. En cada llamada a la función realizamos los siguientes pasos:

  1. Con los parámetros calculamos una clave única
  2. Comprobamos si ya tenemos un resultado asociado a esa clave
  3. Si lo tenemos devolvemos ese valor.
  4. Si no lo tenemos llamamos a la función
  5. Almacenamos el resultado de la función asociados a la clave calculada

La idea es calcular una clave única a partir de los parámetros que se pasan a la función y almacenar el resultado de la primera vez que se le llama para luego las las siguientes veces poder devolverlo sin calcularlo recuperándolo de la memoria.

Hay que ser consiente de que esta técnica permite una ventaja: reducir el tiempo de ejecución y pero con dos penalizaciones: la primera es que la mejora solo se consigue a partir de la segunda vez que se llama a la función, la primera vez se incrementa el coste puesto que se añade el tiempo de cálculo de la clave y de almacenar el resultado en memoria. La segunda penalización es que requiere espacio en memoria para guardar los resultados.

Esto solo funciona si la función cumple ciertas condiciones:

  • El resultado de la función depende única y exclusivamente de los parámetros que se le pasan.
  • Siempre devuelve el mismo resultado para los mismos parámetros.
  • La única funcionalidad que realiza la función es calcular el resultado.
  • Se llama varias veces a la función con los mismo parámetros.
  • El coste de calculo del resultado es mayor que el coste de calcular la clave y buscar el resultado almacenado en memoria.
  • Se intenta optimizar el tiempo de ejecución sobre el uso de memoria.

Ejemplo de implementación

Vamos a ver como implementar una clase JS para aplicar memoization.El código completo se puede encontrar en su repositorio de github.

La memoization tiene dos elementos principales, la función sobre la que se va a aplicar y como se calcula clave a partir de los argumentos que se pasan a esta función. En el constructor de la función vamos requerir estos dos argumentos, si bien el segundo tiene un valor por defecto. También es necesario inicializar la cache donde se guardaran los resultados.

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

Vamos a ver un ejemplo de función que calcula la clave para almacenar el resultado. En la versión por defecto lo que se hace es generar un array con todos los parámetros y convertirlos a un cadena de texto JSON.

    calculateKey(){
        let args = Array.from(arguments);
        return JSON.stringify(args);
    }

Tenemos, la función y la forma de calcular la clave. Nos queda como gestionamos la cache. Los pasos son:

  1. Calcular la clave
  2. Recuperar el valor asociado a esa clave
  3. Si el valor existe se devuelve
  4. Si no existe se calcula
    call(){ 
        let key = this.calculateKey(...arguments);
        if(this.isInCache(key)){
            return this.cache[key];
        } else {
            let result = this.func(...arguments);
            this.add(key, result);
            return result;           
        }
    }

El código completo:

class Memo {
    constructor (func, keyFunc) {
        this.func = func;
        this.cache = {}; 
        this.calculateKey = keyFunc || this.calculateKey;
    }
    call(){ 
        let key = this.calculateKey(...arguments);
        if(this.isInCache(key)){
            return this.cache[key];
        } else {
            let result = this.func(...arguments);
            this.add(key, result);
            return result;           
        }
    }
    calculateKey(){
        let args = Array.from(arguments);
        return JSON.stringify(args);
    }
    isInCache(key){
        return key in this.cache;
    }
    add(key, value){
        this.cache[key] = value;
    }
}

Estrategias para generar la clave de la cache

Hay casos en que varias configuraciones distintas de parámetros son equivalentes. Cuando estos casos se conocen a priori, sin necesidad de calcular el resultado, una mejora puede ser cambiar la función que genera la clave para que tenga en cuenta esta particularidad. Principalmente hay dos estrategias para hacerlo: que la clave que se genera sea la misma para todos estos casos o generar todas las claves que se sabe que tienen el mismo resultado y añadirlas a la cache. Veamos algunos ejemplos.

Propiedad conmutativa: Hay casos en que puede intercambiar el valor de algunos parámetros sin que afecte al resultado. En estos casos una buena optimización es una vez calculado uno de estos casos se aplique a todos los que son equivalentes.

Por ejemplo si la función sum(a,b) realiza la suma de a y b. El resultado será el mismo aunque intercambien valores. sum(2,5) = sum(5,2). Podríamos generar ambas claves “[2,5]” y “[5,2]” y asignarles el mismo resultado.

Otra forma de hacerlo seria ordenar los parámetros de menor a mayor así sum(2,5) y sum(5,2) generarían la misma clave: “[2,5]”.

Aproximaciones de números con decimales: En muchos casos la diferencia entre dos números decimales es tan pequeña que prácticamente no va a afectar al resultado. Para evitar calcular un resultado cuando no es necesario se puede calcular la clave limitando el numero de decimales.

Por ejemplo si fijamos que número máximo de decimales sean 2, tanto sum(3.141592 , 2) como sum(3.1416 , 2) generarían la misma clave “[3.14,2]”

Cadenas de texto: Otro de los casos habituales es que haya cadenas de texto que son equivalentes. Un caso habitual son las mayúsculas y minúsculas, en muchos casos no influye en el resultado. En un caso así search(“juan”) y search(“Juan”) deberían de generar las dos la misma clave: “[juan]”

Datos estructurados: Un caso especial es el de los argumentos que son alguna estructura de datos y que pueden tener muchas formas equivalentes. Por ejemplo, si uno de los parámetros que recibe es un JSON con dos valores “a” y “b” estas dos formas serian la misma: {“a”:1,”b”:2} y {“b”:2,”a”:1}. Llevando el ejemplo más lejos, si la funciona solo va mirar los campos “a” y “b” el caso {“a”:1,”b”:2,”c”:3} seria idéntico. Otro caso son los arrays cuando el orden de los elementos dentro de él da igual. Estos casos son mucho más difíciles de tratar ya que no se puede poner una regla que siempre sea válida y habrá que buscar soluciones propias para cada caso.

Como ejemplo podemos ver un par de ejemplos de como calcular la clave para la cache.

El primero ordena los argumentos que se le pasan;

function calculateKeySorted(){
    let args = Array.from(arguments).sort();    
    return JSON.stringify(args);
}

El segundo convierte los argumentos a minúsculas:

function calculateKeyToLowerCase(){
    let args = Array.from(arguments);    
    return JSON.stringify(args).toLowerCase();
}

Comparar pantallas para encontrar errores visuales durante los test del software

Uno de los problemas de los test automáticos para probar aplicaciones es que no se guían por el aspecto visual de la aplicación. Puedes tener un botón torcido, un texto que no se lee o un color que no corresponde y los test serán correctos, sin embargo para el usuario es importante que el botón que tiene que pulsar sea visible o que pueda leer ese texto ilegible.

Es una tarea difícil de automatizar. Programar código para comprobar la correcta disposición de todos los elementos es algo muy costoso. Y tener una IA que detecte posible elementos erróneos puede sonar muy interesante pero es aun más costoso y complicado. Así que vamos a optar por una solución más sencilla, comparar una imagen que sabemos que esta bien con una captura de pantalla de la aplicación durante las pruebas. Luego calcularemos las diferencias entre ambas imágenes.

Hay múltiples librerías para realizar la comparación de la imagen como pixelmatch o Resemble.js. Aunque estas librerías tienen bastante funciones el algoritmo básico de comparación es muy sencillo. Se comparan uno a uno los pixel de la imagen de referencia con los de la imagen capturada, se fija un valor de umbral. Si la diferencia entre ambos pixel es mayor que el valor umbral se considera que ese pixel es distinto y se marca.

Para que funcione bien la imagen que se usa como modelo ha de ser idéntica a la que se obtiene de la captura de pantalla. Podéis estar pensando que siempre se puede reescalar una de la dos imágenes pero generalmente eso mete “ruido” en los bordes de los elementos de la imagen que el algoritmo de comparación detecta como diferencias.

Los pasos a segir son los siguientes:

  1. Captura de pantalla
  2. Cargar imagen modelo correspondiente
  3. Crear una tercera imagen comparando los pixeles de las dos anteriores. Cada pixel cuya diferencia supere cierto valor fijado se marcara como “diferente”, por ejemplo poniéndolo en rojo.
  4. Buscar si existe alguna diferencia en la imagen resultado de la comparación
  5. Si existe alguna diferencia se deja la imagen con el resultado de la comparación y se avisa al usuario para que la revise.

El principal problemas son los datos que cambian cada vez que se realiza el test. Por ejemplo: ids de elementos, fechas, horas, elementos generados en parte o completamente por procesos (pseudo)aleatorios. Estos elementos dejan “una mancha” en el resultado de la comparación. Una solución para evitar estas manchas es “taparlas” para ello vamos a usar plantillas con una zona de un color especial que cuando el algoritmo las lee ignora.

Otra técnica para ignorar “pequeñas cantidades de error” es dividir la imagen en cuadrados, por ejemplo de 16×16. En cada cuadrado hay un total de 256 pixeles, solo lanzaremos una advertencia si la cantidad de pixeles marcados como diferentes supera cierto número. Por ejemplo el 20%, o lo que es lo mismo 51 pixeles. Con esto logramos que solo diferencias “notables” se consideren.

Este es un sistema realmente simple para ayudar a resolver el problema de comprobar el aspecto visual de la aplicación o la web durante los test. Si bien no resuelve todo el problema y sigue siendo necesario un humano para verificar las imágenes marcadas como errores. Agiliza mucho el proceso de comprobar el correcto aspecto visual de la aplicación o web.

Simular la curva del olvido

Ya hemos visto como hacer algoritmos que “olvidan” con el tiempo, es decir que daban mayor relevancia a los valores recientes que a los más antiguos. Ahora vamos a fijarnos en un modelo de como el cerebro olvida para intentar simularlo. Es un modelo bastante simplificado, proviene de distintos experimentos prácticos basándose en los realizados por Hermann Ebbinghaus estos experimentos parten de memorizar una lista de palabras o silabas sin relación entre ellas. Para lograr que fuera un ejercicio puro de memoria los primeros experimentos usaban listas de silabas inventadas. El sujeto memorizaba la lista y a intervalos de tiempo se pedía recordar todas las que pudiera y se iba contando las que olvidaba.

De estos experimentos se han sacado varias formulas que tratan de aproximar el comportamiento del olvido. Actualmente se acepta como una buena aproximación la formula:

R = e (-t/S)
  • R es el porcentaje de la palabras que se recuerda
  • e es, pues eso, el número e
  • t es el tiempo
  • S la intensidad del recuerdo
e ^-t/S En azul S = 0.5 En verde S = 1

Esta ecuación es la misma que ya vimos en el post sobre algoritmos que olvidan. Solo que allí la formula tenia otra forma:

R = e -kt

En este caso k = 1/S . Esto le confiere propiedades muy útiles que luego veremos.

Modelo Básico

Vamos a empezar por simular el caso más sencillo. Un listado de sílabas aleatorias como el del experimento. En este caso el parámetro S es para todas el mismo. Y todas tiene la misma probabilidad de ser olvidadas en el momento T.

Para saber si una palabra se sigue recordando o no en el momento T se calcula un número al azar entre 0 y 1. Si el número es mayor que el valor de e ^ -t/S esa palabra se olvida. Si es menor se recuerda. Solo queda repetir este proceso por cada palabra del documento.

Mejorando el modelo

Lo que ocurre es que pocas veces en la vida hemos de memorizar un texto de esas características. Lo normal es que sea un texto con distintos tipos de palabras. Veamos como modificamos el parámetro S de cada palabra.

Las palabras vacías o stopwords que son palabras que no aportan nada al significado del texto deberían de tener una S muy baja para que se olviden rápidamente. Para este punto necesitaremos un listado de palabras vacías.

Los nombres propios, fechas, lugares y números incrementan el valor de su S para que duren más tiempo. Este punto requiere la extracción de nombres, entidades, fechas y números. Para ello se pueden combinar distintos algoritmos con diccionarios.

Cada vez que una palabra repita en el texto se incrementa un poco el valor de su S. Salvo con las palabras vacías que no incrementan nada su valor.

Las palabras propias del tema del texto incrementan su valor de S un poco. Es decir, si el texto es sobre medicina las palabras relacionadas con la medicina se recuerdan más fácilmente. Esta parte requiere diccionarios de términos de cada tema.

El caso más complicado es el de las palabras nuevas o desconocidas que deberían tener una S menor de la que les corresponde. La mitad, por ejemplo. Este punto es difícil de simular en un ordenador, podemos hacerlo dotándole de un listado de palabras conocidas.

Este algoritmo produce como resultado un texto que pasado cierto tiempo va olvidando de forma aleatoria distintas partes con preferencia por recordar los datos del texto más relevantes y relacionados con la materia del mismo y el conocimiento previo del lector.

Simulando el aprendizaje por repetición

Una de las conclusiones que se sacaron de estos estudios es que mientras no se ha olvidado el texto el volver a memorizarlo cuesta menos tiempo, es lógico pues aún recordamos parte de él y que cada vez su recuerdo permanece más tiempo.

Esto se podría simular haciendo que cada vez que sea “lea” el texto el valor de la S calculado en el apartado anterior se aumente en un 40% o 50%. Sn = S * (1+(0.5*n)) siendo n el número de relecturas del texto.

Raspberry Pi, desactivar conexiones para ahorrar energía

Vamos a ver como poder modificar las distintas conexiones que posee una Raspberry Pi para ahorrar energía. Nos vamos a entrar en

Hay que pensarlo bien antes de usar una política de activar/deasctivar las conexiones. El restablecerlas tiene un coste enérgico por lo que estar todo el rato conectando y desconectando puede aumentar el consumo en lugar reducirlo.

Todos estos comandos se deben de ejecutar con permisos de administrador por lo que puede ser necesario usar sudo delante de ellos.

Desactivar y activar bluetooth

Desactivar bluetooth:

rfkill block bluetooth

Activar bluetooth:

rfkill unblock bluetooth

Desactivar y activar WiFi

Desactivar WiFi:

rfkill block wifi

Activar WiFi

rfkill unblock wifi

Al activarlo habrá que volver a conectarse a la red.

Conexión ethernet

En este apartado vamos a ver como de desactiva el software a nivel de sistema operativo y no como desactivar la alimentación que veremos en el apartado siguiente.

Desactivar conexión ethernet:

ifconfig eth0 down

Activar conexión ethernet:

ifconfig eth0 up

Desactivar puertos USB (y ethernet)

Los puertos USB y ehernet tienne alimentación compartida si desactivamos los USB el puerto ethernet “cae con ellos”.

Desactivar puertos USB:

echo '1-1' |sudo tee /sys/bus/usb/drivers/usb/unbind

Activar puertos USB:

echo '1-1' |sudo tee /sys/bus/usb/drivers/usb/bind

Raspberry Pi programar el encendido, apagado y reinicio

Vamos a ver como podemos configurar la Raspberry Pi para que se encienda, se apague y se reinicie cuando nosotros queramos. El reinicio y el apagado son sencillos. Para el encendido necesitaremos soluciones más imaginativas.

Para programar el apagado y reinicio usaremos el servicio cron para programar tareas. Ya vimos como funcionaba en esta entrada. Ahora solo hace falta ver que comandos tenemos que programar.

Para reiniciar la Raspberry podemos usar:

reboot

Y para apagarla:

shutdown -h now

Ambos necesitan usarse con permisos de administrador por lo que es recomendable programarlos en el crontab del usuario root o de cualquiera que tenga permisos de administrador y precederlos del comando sudo.

Ahora vemos como resolvemos programar el encendido. No hay un comando que encienda al Raspberry Pi tras haberla apagado. Para ello necesitáramos un RTC con una batería que se quedase en espera para encender la Raspberry, por suerte debido a como funciona la Raspberry eso es muy sencillo. Si tienes la Raspberry sin alimentación y se la pones se enciende, no hay ningún interruptor que cambiar de on a off ni nada parecido. Aprovechando eso basta con usar un enchufe con temporizador, que se pueden comprar muy baratos. Esos enchufes llevan un reloj en el que ajustas que horas van a tener luz y que horas no.

Por ejemplo, supongamos que queremos tener una Raspberry de tal forma que se apague por la madrugada cuando no vas a hacer uso de ella, por ejemplo a la 1:00 y que se encienda a las 8:00. Lo primero es ajustar el enchufe, lo programamos para que corte la alimentación pasadas la 1:00 pero dejando un margen, la 1:15, la 1:30, sin apurar. Y para que a las 8:00 (o algo antes) vuelta a conectar la alimentación.

El último paso es configurar una tarea de cron para que lance el comando de apagar la Raspberry a la 1:00. Para evitar que cuando se corte la corriente se apague la Raspberry mal hay que dejar un margen de tiempo desde que está programado lanzar el comando hasta que el enchufe corte la corriente.

Raspberry Pi programar tareas con cron

Cron es un servicio que se usa para lanzar tareas programadas. Su configuración se realiza a través de un fichero de configuración donde se definen las tareas programadas.

Cada usuario tiene su propio fichero de configuración, este se edita con el comando:

crontab -e

Cada linea del fichero es una tarea programada. Una tarea progrmada tiene 6 parámetros (excepto si empieza por @ que solo tiene 2), los cinco primeros definen en momento cuando se lanza el comando y el sexto el comando que se lanza:

  1. Minuto en el que se ejecutará el comando, va de 0 a 59.
  2. Hora en la que se ejecutará el comando, va de 0 a 23.
  3. Día del mes en el que se ejecutará el comando, va del 1 al 31.
  4. Mes en el que se ejecutará el comando, va del 1 al 12.
  5. Día de la semana en el que se ejecutará el comando, va de 0 a 7.
  6. Comando que se ejecutará

Se pueden usar los siguientes modificadores:

  • * : cualquier valor de ese campo
  • , : define listas de valores. 1,3,5
  • – : define rangos de valores. 1-5 seria lo mismo que 1,2,3,4,5
  • / : en combinación con un rango o el asterisco define “cada cuanto”. 1-5/2 seria lo mismo que 1,3,5
  • # : Se usa en el campo día de la semana, indica que semana del mes. 1#2 el segundo lunes del mes
  • W : Se usa en el campo día de la semana, indica el primero del mes. 1W el primer lunes del mes
  • L : Se usa en el campo día de la semana, indica el último del mes. 1L el último lunes del mes
  • @ : Es un atajo para indicar momentos concretos
    • @reboot una vez al inicio del sistema
    • @yearly una vez al año “0 0 1 1 *”
    • @annually una vez al año “0 0 1 1 *”
    • @monthly una vez al mes “0 0 1 * *”
    • @weekly una vez a la semana “0 0 * * 0″
    • @daily una vez cada dia “0 0 * * *”
    • @hourly una vez cada hora “0 * * * *”

Algunos ejemplos:

30 22 * * 1-5 reboot

Reiniciar de lunes a viernes a las 22:30 (las diez de la noche)

30 10-14/2 * * * reboot

Reiniciar a las 10:30, 12:30 y 14:30 cada día

15 */6 * * * reboot

Reiniciar cada 6 horas cuando sean las y cuarto de esa hora

@hourly reboot

Reiniciar cada hora

30 10 * * 0W reboot

Reiniciar el primer domingo de cada mes

30 10 * * 7W reboot

Reiniciar el primer domingo de cada mes (el 0 y el 7 indican domingo)