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:
-
- Horario normal, N: encendido a las 9 y apagado a las 20
- Horario de mañanas, M: encendido a las 9 y apagado a las 15
- Horario de sábados, S: encendido a las 9 y apagado a las 13
- 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:
-
- 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.
-
- El segundo paso será comprobar si es horario de mañanas, y si lo es, cambiar $hoy a «M».
- El tercer paso será comprobar si es vacaciones o festivo, en cuyo caso asignaremos $hoy a «F»
- A continuación comprobamos si es sábado o domingo y de serlo asignamos a $hoy el valor «F»
- 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í:

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

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
Hola de nuevo estimado Carlos, estoy algo liado con la parte de la programación de días, me gustaría omitir esta sección para hacer que siempre se inicie apenas conectar la energía eléctrica al raspberry, pero no se como hacer esa parte, o como configuro el archivo de días para que siempre funcione?
muchas gracias, saludos.
Hola Ismael
Esto es fácil, que sólo hay que quitar cosas.
En la parte de programado de encendido… aquí:
https://www.wampirius.com/pantallas-informativas-vii-programado-de-encendido-y-actualizaciones-con-crontab/
Verás que a la hora de que se encienda la Raspi se ejecutan varios scripts, entre ellos actualizadia.sh y fecha.sh
El primero es el que mira a ver si ha habido cambios en los calendarios y el segundo es el que comprueba qué día es, según los archivos de los calendarios y al final lanza el script proyecta.sh pasándole el argumento correspondiente: N, F… el que sea.
Vale. Pues como esto no lo necesitas para nada modifica en crontab lo siguiente:
En la primera línea (@reboot):
1.- Quita lo referente al fichero actualizadia.sh como si no existiera.
2.- Cambia al final fecha.sh por proyecta.sh
3.- Quita también las dos líneas que hacen referencia a esos ficheros
4.- Te debería quedar algo así:
@reboot sleep 10 && /home/pi/iniciando.sh > /dev/tty1; /home/pi/actualiza.sh; /home/pi/proyecta.sh &
#
*/15 * * * * /home/pi/queip.sh &
#
*/30 * * * * /home/pi/actualiza.sh &
Ahora hay que modificar el script proyecta.sh pero lo mismo de antes, es quitar, así que sin miedo:
Al principio verás que hay un “case” en la línea 16 que está comentado en la anterior, bien, pues puedes borrar o comentarlo:
#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
Todo eso fuera.
Y luego hay que quitar la parte que “apaga” la Raspberry según lo anterior. Eso está a partir de la línea 32 con los comentarios, el if que hay:
#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
Todo eso también fuera, o comentado.
Y por si acaso, para que no de error y dejar las cosas en su sitio, modifica también el fichero actualiza.sh quitando las líneas relativas a la actualización del calendario: la 43 y 44:
printf “\nCalendario ctualizado a: ” >> /home/pi/report.txt
cat /home/pi/calendario.txt >> /home/pi/report.txt
Y ya está, con eso debería bastar para olvidarnos del calendario… si es que no soy yo el que se olvida de algo.
Espero que te sirva. Ya me contarás.
Un cordial saludo.
Carlos.