Pantallas informativas (VI): Distintos horarios de funcionamiento según la fecha

Para hacer el sistema lo más autónomo posible, lo suyo sería que se encendiera y apagara a la hora programada, e incluso, que el encendido y apagado pudiera variar según determinadas fechas.

Este proyecto está diseñado para varios edificios con una apertura al público en diversos horarios según la época del año, es un tanto particular, así que vamos a buscar un ejemplo que lo haga más comprensible.

Vamos a imaginarnos un comercio que abre la persiana a las 9 y la baja a las 20 horas, que abre determinados sábados de 9 a 13, algunos sueltos, y que para el verano tiene días de apertura sólo de mañanas, de 9 a 15, por último este comercio cierra los días de vacaciones como es lógico, y los domingos y festivos.

Vamos a ver cómo programar todo esto, que parece un lío pero no es para tanto. De momento lo planteamos

Tenemos cuatro posibilidades:

    1. Horario normal, N: encendido a las 9 y apagado a las 20
    2. Horario de mañanas, M: encendido a las 9 y apagado a las 15
    3. Horario de sábados, S: encendido a las 9 y apagado a las 13
    4. Festivos, F: apagado todo el día

Estamos en Zaragoza (España), es 2019 y el calendario que nos pasan podría ser como el siguiente:

En rojo están los festivos, sábados y domingos; en azul las vacaciones, en amarillo los días que abre sólo de mañana y en verde los sábados que tiene apertura.

Vamos a preparar un script que haga lo siguiente:

    1. Partiremos suponiendo que el día es normal y asignaremos a una variable, $hoy, el valor “N”

A partir de aquí haremos una serie de comparaciones if-then, no if-elif-else, pues el valor puede cambiar varias veces, que parece una estupidez pero es por comodidad, luego lo veremos más claro.

    1. El segundo paso será comprobar si es horario de mañanas, y si lo es, cambiar $hoy a “M”.
    2. El tercer paso será comprobar si es vacaciones o festivo, en cuyo caso asignaremos $hoy a “F”
    3. A continuación comprobamos si es sábado o domingo y de serlo asignamos a $hoy el valor “F”
    4. Y por último comprobamos si estamos ante un sábado de apertura, en cuyo caso asignamos a $hoy el valor “S”

Bien, hemos hablado de comprobar, comprobar, comprobar… ¿pero cómo comprobamos? Pues con un un listado de fechas para cada tipo de día. Necesitaremos en este caso: Un fichero con las fechas de los festivos y días de vacaciones, otro con los días que sólo se abre de mañanas y un tercero con los sábados que se trabaja. Una vez que tengamos estos ficheros el sistema es cargar el contenido en un array y comparar cada registro del array con la fecha en la que nos encontremos.

Ficheros con los listados de fechas

Una forma fácil de hacer la comparación que mencionábamos es usar la nomenclatura Año-mes-día sin separación. Si escribimos en el shell

date -%Y%m%d

veremos que obtenemos la fecha así, saldrá algo como 20190304. Así pues, los ficheros que generemos tendrán que tener la fecha en este formato, uno detrás de otro y separado por un espacio, por ejemplo el de los sábados de apertura tendrá el contenido: 20190105 20190302 20190907 20191005 20191019 y lo guardaremos como sabados.txt

Para el resto de tipos de días haremos lo mismo. Un truquillo es hacerlo con Excel o Calc. Por ejemplo, para los días de apertura sólo de mañanas podemos coger el Excel y en la primera celda (A1) escribir 20190708 y en la de la derecha (B1) poner una fórmula que sume 1 a esta celda (=A1+1)

Luego hacer un llenado hacia la derecha (Ctrl-D) y rellenar las celdas hasta el valor 20190726, es decir, 19 celdas.

Salvamos como texto separado por comas (o punto y coma) y con nuestro editor de texto favorito reemplazamos el carácter separador por un espacio. Luego añadiríamos los días que faltan, del 19 al 23 de agosto, y listo. Ya tenemos un bloque.

Con el de vacaciones haríamos lo mismo, y los días festivos los añadiríamos a mano al fichero. Parece muy lioso pero en realidad es más largo de explicar que de hacer.

Y llegados a este punto puede deducirse por qué antes hemos hablado de usar if-then e ir cambiando el valor. Vemos que no nos hemos preocupado de separar fechas en el mes de julio, considerando del 8 al 26 como que se abre de mañanas, pero entre medio hay sábados y domingos. Haciéndolo así nos despreocupamos de tener que ir seleccionando los días a la hora de crear los ficheros, simplemente al comparar la fecha, por ejemplo, del 20 de julio, primero tomará, por defecto, el valor “N”, luego comparará el fichero de “sólo mañanas” y tomará el valor “M” pero después verá que cae en fin de semana y cambiará de nuevo, esta vez finalmente a “F”.

Bien, pues damos por hecho que ya tenemos tres ficheros, que van a llamarse: “mananas.txt”, “festivos.txt” y “sabados.txt” y los vamos a meter en una carpeta que se llame “dias” en el home de pi en la Raspberry y a preparar el script que se habrá de ejecutar a las 9 de la mañana:

Script fecha.sh

Comenzamos asignando el valor a tres variables:

La variable $dia va a contener el tipo de día que es, comenzando como ya hemos dicho por “N” (normal)

$esfinde la usaremos para saber qué día de la semana es +%u devolverá 1 si es lunes, 2 si es martes, etc. La usaremos para saber si estamos ante un fin de semana.

$hoy tomará el valor de la fecha actual en el formato antes mencionado.

Hacemos ahora la primera comparación para ver si estamos en horario de mañanas:

Y aquí está todo el meollo: Creamos un array en el que metemos el contenido del fichero de texto “mananas.txt”, luego vamos recorriendo con un for-in cada uno de sus elementos y comparándolos con el contenido de la variable $hoy, si alguno coincide asignamos a $dia el valor “M” y si no no hacemos nada.

Y el resto de comparaciones van igual, sólo que con otros nombres:

Se puede ver en la imagen que los bloques de comparación son casi un calco unos de otros a excepción del que comprueba si es sábado o domingo, que lo que hace es comprobar que el valor numérico de la variable $esfinde es mayor que 5.

Vale, ya tenemos el valor de $dia, así que ahora tan sólo tenemos que arrancar nuestro script de visualización de los archivos pasando este valor como argumento, así:

Vamos a llamar a este script fecha.sh y su código completo es el siguiente:

#!/bin/bash
dia="N"
esfinde=$(date +%u)
hoy=$(date +%Y%m%d)
# Comprobamos si se abre solo de mañanas
mananas=$(cat /home/pi/dias/mananas.txt)
for m in $mananas
do
	if [ $m = $hoy ]
	then
		dia="M"
	fi
done
# Comprobamos si es festivo o vacaciones
festivos=$(cat /home/pi/dias/festivos.txt)
for f in $festivos
do
	if [ $f = $hoy ]
	then
		dia="F"
	fi
done
# Comprobamos si es sabado o domingo
if [ $esfinde -gt 5 ]
then
	dia="F"
fi
# Comprobamos si es sábado de apertura
sabados=$(cat /home/pi/dias/sabados.txt)
for s in $sabados
do
	if [ $s = $hoy ]
	then
		dia="S"
	fi
done
# Si no es festivo, arrancamos proyecta.sh
# y pasamos en el argumento el tipo de día
if [ $dia != "F" ]
then
	bash proyecta.sh $dia
fi

Modificando proyecta.sh

Ahora toca hacer los ajustes en el script proyecta.sh que venimos arrastrando desde las anteriores entradas. Al principio del mismo colocamos lo siguiente:

 

La variable $1 contiene el argumento que acabamos de pasar, así que hacemos un case y a la variable $apaga le asignamos el valor numérico que corresponderá a la hora-minutos de apagado.

Una aclaración: aquí bien podríamos haber pasado el valor de la hora de apagado directamente como argumento, haciendo el case en el script que compara los ficheros, e incluso no haciéndolo y asignando 1300 en lugar de “S” a la variable cuando detecte un sábado laborable, por ejemplo, y así con el resto. Pero es todo cuestión de orden y a mí me resulta más cómodo así, por tener todas las variables que pueda definir lo más agrupadas posibles, por si en algún momento tengo que modificar algo.

Bien, pues ya tenemos la variable $apaga con la hora de apagado de la pantalla, ahora nos metemos en el while y comprobamos:

Al inicio del bucle cargamos en la variable $quehora la hora actual del sistema en formato HHMM, acto seguido comparamos si el valor de dicha variable es superior al de la variable $apaga, en caso de serlo cambiamos el valor de la variable que hace que el bucle se ejecute, ponemos $ejecutacon valor “fin” y salimos del bucle.

Obviamente no es un apagado “exacto”, pero el sistema tampoco nos requiere una precisión de cirujano y nos sirve perfectamente.

Y ahora toca apagar la pantalla. Hay varias formas de hacer esto, yendo de lo más fácil a lo más complicado serían:

    • Quitar la señal al HDMI
    • Usar comandos CEC
    • Quitar la corriente a la pantalla con un relé usando un puerto GPIO

Vamos a comenzar probando a, simplemente, desactivar el HDMI. Hay unos cuantos monitores y pantallas que si dejan de recibir señal por HDMI se ponen automáticamente en standby transcurridos unos segundos, pues bien, probaremos a ver si tenemos suerte.

Escribimos esto en el shell. Esto desactivará el HDMI en el que tenemos conectada la pantalla:

vcgencmd display_power 0

Si al cabo de unos segundos la pantalla pasa a standby, fenomenal, yo no me complicaría más y lo dejaría así. Iríamos al final del archivo, donde están los if para realizar las tareas oportunas una vez que hemos salido del bucle, y colocaríamos nuestra condición para apagar:

Y para encender sería la misma instrucción pero con un “1”, que colocaríamos al principio del script:

vcgencmd display_power 1

Sin embargo en nuestro caso eso no ha funcionado, se desactivaba el HDMI pero se quedaba el cartelito de “no signal” ad eternum. Tocaba exprimirse las meninges.

Lo primero fue instalar las cec-utils

sudo apt-get install cec-utils

Y a continuación probar los comandos para apagar y encender:

Apagado:

echo standby 0 | cec-client -s -d 1

Para encender:

echo on 0 | cec-client -s -d 1

Y sí, eso en nuestra pantalla de pruebas, una Panasonic modelo viejo, gordo y pesado, funcionó. O casi, porque al encender se iba al canal de TV. Tras unas cuantas pruebas el comando que funcionó para cambiar al HDMI 1 que era donde estaba conectada la Raspberry fue el siguiente:

echo 'tx 4f:82:11:00' | cec-client -s -d 1

Sinceramente no puedo explicar bien estos comandos. Los de encendido y apagado lo he visto en varias páginas de Internet, los he probado y funcionan, el de cambio a HDMI ya es un poco “refrito y prueba”. Ayudado por la página: http://www.cec-o-matic.com/ “4” sería “fuente: playback 1”; “f”: “destino: broadcast”; “82”: “Used by a new source to indicate that it has started to transmit a stream OR used in response to a “Request Active Source” (Brodcast)”; y “11:00”: “physical address” que por lo que tengo entendido, con esos valores corresponde a HDMI 1 que es donde está pinchada la Raspberry.

Así que, con un poco de deducción y otro poco de la técnica que usó el burro del cuento para hacer sonar la flauta, el caso es que la tele se enciende, se apaga y conseguimos pasarla al HDMI que nos interesa. Así que nuestro if para apagar tiene que quedar así:

De paso añadimos también el apagado de pantalla si salimos con una opción que no sea la 003:

Y por supuesto añadimos el encendido de pantalla al principio del fichero con las dos instrucciones mencionadas: la de encendido y la de conmutar a HDMI 1

Y si esto tampoco funciona, entonces el asunto ya se nos complicaría un poco. Tendríamos que programar una salida del GPIO para que activara un relé que cerrara el paso de corriente de la pantalla, y que esta se encendiera cuando se activara el relé y se apagara cuando el relé le cortara la corriente. Pero ojo, además necesitaríamos que al encender así, a lo bruto, la pantalla se activara completamente y no quedara en standby que es como muchas suelen quedar si las desenchufamos a las bravas y las volvemos a enchufar, de ser así, de quedarse en standby, ya nos podemos ir olvidando porque no tendremos más remedio que usar el mando a distancia al no poder controlarla por HDMI de ninguna manera.

Y habréis visto que en el script hemos configurado el horario de apagado de las pantallas, pero no el de encendido. Eso lo haremos con crontab y lo trataremos en la siguiente entrada, pero antes de pasar a ella nos queda todavía algo pendiente ¿cómo actualizamos los archivos de fechas en la Raspberry? Necesitamos otro script.

Actualización de fechas en la Raspberry

Este va a ser fácil, será una modificación del script actualiza.sh La Raspberry comprobará si existe alguna diferencia entre los ficheros almacenados en las carpetas dias del servidor y la Raspberry, tal como se hace con los archivos a proyectar. La única diferencia es que aquí prescindiremos de la opción -delete y no borraremos nada .

Bueno, hay más diferencias, el script sólo nos mandará un correo si realiza alguna actualización en los archivos, en caso contrario no nos molestará. Lo que sí que hará será guardar la fecha y hora de cualquier actualización o comprobación exitosa, en un fichero que llamaremos calendario.txt, así, ante cualquier duda, podremos pedir un test y sabremos cuando se han actualizado o verificado estos archivos. En proyecta.sh lo integraríamos así:

Fragmento de “proyecta.sh”. Hemos insertado en las líneas 66 y 67 la información relativa a la fecha y hora de la actualización o verificación de los ficheros de fechas

Vamos ya con el script para actualizar el calendario, le llamaremos actualizadia.sh

Script muy sencillo para la actualización de los ficheros de fechas en la Raspberry. La verdad es que podría estar más currado, con algún control para verificar que todos los archivos necesarios existen en el servidor, las fechas están actualizadas, etc. pero en fin, eso lo dejaremos para una posterior actualización.

Vemos que la conexión y la actualización se realiza como ya hemos visto antes, aunque con las carpetas de los calendarios. En la línea 11 se graba la fecha si la actualización o verificación ha sido correcta y luego en la línea 18 al comparar los contenidos de los ficheros que contienen el antes y el después, sólo se mandará un mail si existe alguna diferencia.

Este script haremos que se ejecute diariamente a eso de las 6 am, a fin de que no interfiera con ninguna tarea.

Aquí está el código para copy&paste:

#!/bin/bash
nombrepantalla=$(hostname)
correo="[email protected]"
#Volcamos el contenido de los ficheros de "dias" en dias.old
cat /home/pi/dias/* > /home/pi/dias.old
rsync -rtvu --timeout=60 -e "ssh -i /home/pi/.ssh/miclave" [email protected]:/home/pi/datos/dias/ /home/pi/dias/
if [ $? -eq 0 ]
#Si no hay errores apuntamos en calendario.txt la fecha de comprobación
then
	resultado="Actualización calendario correcta"
	date > calendario.txt
fi
#Volvemos a volcar el contenido de los ficheros de "dias" esta vez en dias.new
#y comparamos los ficheros, si hay diferencias mandamos un correo
#así sólo se enviará un correo cuando haya una actualización exitosa
#y que modifique alguno de los archivos
cat /home/pi/dias/* > /home/pi/dias.new
diff -q /home/pi/dias.old /home/pi/dias.new > /dev/null
if [ $? -ne 0 ]
then
	hostname -I > /home/pi/ip.new
	cp /home/pi/ip.new /home/pi/ip.old
	printf "$resultado en $nombrepantalla\n\n" > /home/pi/report.txt
	printf "\nIp: " >> /home/pi/report.txt
	cat /home/pi/ip.new >> /home/pi/report.txt
	printf "\nActualizado a: " >> /home/pi/report.txt
	cat /home/pi/calendario.txt >> /home/pi/report.txt
	mutt -s "$resultado en $nombrepantalla" $correo < /home/pi/report.txt
fi

Para terminar, aquí está el código actualizado del script proyecta.sh con los últimos cambios que hemos visto en esta entrada:

#!/bin/bash
#Establecemos en estas variables:
#El nombre de la pantalla a gestionar
#El correo donde mandar los informes
#La variable $ejecuta: bucle, salir, apagar...
nombrepantalla=$(hostname)
longitud=${#nombrepantalla}
correo="[email protected]"
ejecuta="bucle"
#Almacenamos en la variable $apaga la hora de apagado según el tipo de día
case $1 in
	'N') apaga=2000 ;;
	'M') apaga=1500 ;;
	'S') apaga=1300 ;;
esac
#Encendemos la pantalla y seleccionamos HDMI 1
echo on 0 | cec-client -s -d 1
echo 'tx 4f:82:11:00' | cec-client -s -d 1
#Ocultamos el cursor
echo -e "\e[?1;0;0c"
clear
#Ejecutamos el bucle mientras $ejecuta sea "bucle"
while [ $ejecuta = "bucle" ]
do
#Comprobamos la hora para apagar la pantalla
#Si la sobrepasa cambiamos el valor de la variable $ejecuta y salimos del bucle
#Fuera del bucle compararemos el valor de la variable y apagaremos
quehora=$(date +%H%M)
if [ $quehora -gt $apaga ]
then
	ejecuta="fin"
	break
fi
#Refrescamos el directorio
cd .
#Recorremos el directorio
for file in /home/pi/Videos/*
do
#Metemos en variable el nombre y la extensión
extension="${file##*.}"
filename=$(basename $file)
fname="${filename%.*}"
#A continuación usaremos nombres de archivo que reservaremos para ejecutar diversas acciones
#Si el nombre coincide con el nombre de la pantalla pararemos el bucle
#borraremos el fichero y luego ejecutaremos la acción relacionada
if [ $fname = $nombrepantalla ]
	then
		ejecuta=$extension
		rm $file
		break
#Si el fichero comienza por XXX pero a continuación no se encuentra el nombre
#de la pantalla, lo ignoraremos.
elif [ ${fname:0:3} = "XXX" ] && [ ${fname:3:$longitud} != $nombrepantalla ]
	then
		:
#Si el nombre del fichero es test preparamos un informe que nos diga
#los ficheros que contiene el directorio, IP y la hora de actualización
elif [ $fname = 'test' ]
	then
		printf "Test de $nombrepantalla\n\nArchivos en directorio:\n\n" > /home/pi/report.txt
		ls /home/pi/Videos >> /home/pi/report.txt
		printf "\nIp: " >> /home/pi/report.txt
		hostname -I >> /home/pi/report.txt
		printf "\nActualizado a: " >> /home/pi/report.txt
		cat /home/pi/actualizacion.txt >> /home/pi/report.txt
		printf "\nActualizado a: " >> /home/pi/report.txt
		cat /home/pi/calendario.txt >> /home/pi/report.txt		
		mutt -s "Test $nombrepantalla" $correo < /home/pi/report.txt
		rm $file
#A partir de aquí suponemos que ya son ficheros para proyectar
#Si el fichero tiene extensión jpg lo mostramos en pantalla la duración establecida
elif [ $extension = 'jpg' ]
	then
		clear
		fim $file -a -c '{sleep 20;quit;};'
		clear
#En cualquier otro caso asumimos que se trata de un video y lo reproducimos
#Por defecto, al ser pantallas informativas,
#reproduciremos el vídeo sin sonido
#Si queremos sonido basta con poner al final del nombre de archivo AAA justo antes del punto de la extensión
else
	if [ ${fname: -3} = "AAA" ]
	then
		omxplayer  -b --blank --aspect-mode letterbox "$file" > /dev/null
	else
		omxplayer  -b --blank --aspect-mode letterbox --vol -6000 "$file" > /dev/null
	fi
fi
done
done
#Si salimos del bucle puede ser por algún fichero del tipo
#nombrepantalla.extensión y la extensión la tenemos en variable ejecuta
#La extensión deberá ser 001, 002, etc y según sea se ejecutará lo siguiente:
#Si es 001 apagamos la Raspberry
if [ $ejecuta = '001' ]
then
	sudo shutdown -h now
#Si es 002 reiniciamos:
elif [ $ejecuta = '002' ]
then
	sudo shutdown -r now
#Si es 003 borramos
elif [ $ejecuta = '003' ]
then
	clear
#Si hemos salido porque hay que apagar la pantalla:
elif [ $ejecuta = 'fin' ]
then
	echo standby 0 | cec-client -s -d 1
#En cualquier otro caso termina el script y borramos la pantalla
else
	clear
	echo standby 0 | cec-client -s -d 1
fi
#Podemos ir añadiendo las condiciones que queramos con elif
#Debemos evitar los ficheros de imagen y video con el nombre de la pantalla
#o el script se detendrá

Ficheros tratados en esta entrada

Listados con fechas

    • mananas.txt: fichero de texto con los días de apertura matinal
    • festivos.txt: fichero de texto con los días festivos
    • sabados.txt: fichero de texto con horario de sábados

Fichero de control de actualización de fechas

    • calendario.txt

Scripts

    • fecha.sh: script que activa o no proyecta.sh con argumento
    • actualizadia.sh: script que actualiza los calendarios de días en la Raspberry
    • proyecta.sh: modificación del script principal

Deja un comentario

No está permitida la inserción de ningún dato de carácter personal (mail, tef...). Cualquier comentario que los contenga será editado o eliminado.

Por favor, si dejas un comentario pon cualquier nombre para poderme dirigir a ti pero que no te identifique

1 × 3 =

El comentario es totalmente privado. No se almacena la IP