- Se incorporó
- 2 Octubre 2005
- Mensajes
- 13.604
Resulta que hace como un mes atrás di con un video en YouTube el cual mostraba todas las formas en que uno podía re-ocupar las piezas de un notebook viejo, así que recordando que el pasado siempre fue mejor, revolví cielo mar y tierra hasta poder encontrar ese notebook que hacía años se había echado a perder pero que nunca lo llegué a botar pensando en que algún día me iba a poder servir. Ja, de algo que me sirva ser cachurero! Yo sabía perfectamente hace 7 años atrás que lo iba a necesitar para deshuesarlo!
La cosa es que lo desarmé por completo y terminé sacando la pantalla completa que aún estaba intacto. Sin poder probar si todavía funcionaba, me fijé que el monitor era un Samsung LTN154X3-L01 así que tío AliExpress me ayudó a encontrar un driver para ese monitor en particular. Suerte la mía, sólo costaba €19, así que lo compré inmediatamente junto con una fuente de 12v/5A para poder alimentarlo. La verdad el monitor sólo necesita una fuente de 3A, pero más adelante pretendo utilizar esa misma fuente para alimentar algunas otras cosas. (Música de suspenso maestro!)
También rescaté la webcam del fallecido notebook para de esa forma tener ojos digitales en el living que era el lugar donde quería poner mi monitor.
Acto seguido fue ver de qué tamaño era el monitor para poder hacerle un frame de madera. Luego de medir todo, me dispuse a cortar diversos palos en 45° y expuse todas las piezas para ver qué iba a quedar donde:
Ya teniendo una idea más certera de cómo iba a ser la construcción, hice los slots para que el monitor quedara embebido en el frame:
A su vez, también me dispuse a probar la cámara, la verdad no sabía muy bien si esto iba a funcionar, pero por lo general las webcam son todas USB... así que si lo conectaba a un cable USB era probable que funcionara... y así fue! Lo único sí es que tuve que dar vuelta los cables de datos pq lamentablemente el estándar USB no define los colores de los cables de datos, así que su experiencia puede ser distinta:
Teniendo claro el tamaño y ubicación de la webcam me dispuse a crear un slot para que cupiera. Había hecho algunas pruebas y la cámara funcionaba de lo más bien metiéndolo en un hoyo de 6mm de profundidad, así que eso hice, primero taladré el hoyo y luego me dispuse a sacar material para poder embeber la cámara:
Terminado esos dos pasos, la idea final fue surgiendo:
Ahora empezaba la espera... del coronavirus el primer caso se presentó en Francia hacía sólo 5 días, el movimiento social en Chile estaba en una calma típica antes de la tormenta y el 14 de febrero se iba a celebrar en 14 días más... Sin embargo, recién el 14 de marzo pude seguir con el proyecto ya que por fin aparecieron todas las piezas que necesitaba.
Como ya tenía gran parte del proyecto listo, me dispuse a probar que el monitor funcionara y para mi tranquilidad funcionó bien a la primera! Así que ya con eso confirmado, me dispuse a hacer mierda la puerta donde iba a colgar el monitor. La idea era que la controladora quedara afirmada por el reverso de la puerta mientras que el monitor quedara al anverso.
Por el reverso:
Así y todo, así es como quedó aquel primer día:
Finalmente llegó la hora de prender el monitor y por supuesto apreciar el booteo en pleno:
Ahora bien, me imagino que querrán saber qué cresta hay al otro lado de esa puerta: pues muy convenientemente, llegan los cables eléctricos y de la compañía de tv e internet, así que es un lugar ideal para colgar una raspberry pi 4 que había adquirido hace un tiempo (favor omitir el mar de cables, algún día lo ordenaré como se debe):
Aquí venía la parte divertida: configurar la rpi para que pudiera levantar un entorno gráfico (cuando lo instalé lo hice sin entorno gráfico pq no pensé que lo iría a ocupar algún día) e iniciara un navegador en modo kiosk mostrando cierta página web. Para esto, ejecuté lo siguiente en la rpi:
Código:
sudo apt install raspberrypi-ui-mods
sudo raspi-config
En raspi-config es muy importante ir a "7 Advanced Options" y activar el soporte experimental para el GL Driver, de otra forma les va a ser imposible poder rotar la pantalla 90° o 270° y aumentar la resolución a la resolución nativa del monitor.
Aprovechando que están ahí, para que bootee en modo gráfico, vayan a "3 Boot Options" y en B1 eligen la opción B4 para que se logee automáticamente a la cuenta pi del sistema mientras está en el modo gráfico.
Nótese que en la rpi4 y con el último build de raspbian, cambiaron bastante la forma en que los gráficos que se manejan, así que les dejo el dato que a partir de la rpi4 esta es la forma oficial de rotar la pantalla via CLI:
Obviamente tienen que ajustar ahí los parámetros correspondientes.
Código:
DISPLAY=:0 xrandr --output HDMI-1 --rotate left
Obviamente tienen que ajustar ahí los parámetros correspondientes.
Una vez rotada la pantalla, llegó la hora de poder iniciar el browser. Lo primero que hice fue instalar Chromium ya que soporta el modo kiosko de forma nativa, pero dp de un rato me empezó a salir el mensaje de que tenía que actualizarlo y no encontré una forma fácil de deshabilitar eso, así que terminé instalando firefox-esr con dos plugins: una para establecer el zoom predeterminado del navegador y el otro para iniciar firefox en pantalla completa. Para que ejecutara el programa creé el siguiente script:
Código:
#!/bin/bash
sleep 45
DISPLAY=:0 /usr/bin/firefox-esr https://mm.home.unreal4u.com/ &
El sleep está ahí para que docker tenga tiempo de levantar la imagen antes de que firefox se active, de otra forma tendré en pantalla un lindo error 502 (Según me contó un amigo). Este script lo llamo en mi /etc/xdg/lxsession/LXDE-pi/autostart:
Código:
@/home/pi/start-kiosk.sh
También hice un script que reiniciara firefox cada 24 horas en caso de problemas o actualización, este simplemente mata cualquier proceso con nombre firefox-esr y ejecuta el script de arriba. Convenientemente, le da 45 segundos a firefox para salir de su sesión.
De ahí lo otro que hice fue "instalar" Magic Mirror... en realidad simplemente lo hice con Docker, mi docker-compose.yml se ve así:
Código:
version: "3"
services:
nodered:
container_name: nodered
image: "nodered/node-red:1.0.3-2-12-minimal-arm32v7"
restart: unless-stopped
environment:
- TZ=Europe/Amsterdam
ports:
- 1880:1880
volumes:
- ~/iot-services/services/nodered:/data
magicmirror:
container_name: magicmirror
image: "bastilimbach/docker-magicmirror"
restart: unless-stopped
ports:
- 8099:8080
volumes:
- /etc/localtime:/etc/localtime:ro
- ~/iot-services/services/magicmirror/config:/opt/magic_mirror/config
- ~/iot-services/services/magicmirror/modules:/opt/magic_mirror/modules
- ~/iot-services/services/magicmirror/custom.css:/opt/magic_mirror/css/custom.css
Eso instala dos imágenes de Docker: una es node-red (del cual hablaré muy brevemente en este post, pero estoy preparando otro donde se tocará en mayor profundidad) y la otra es MagicMirror.
Lo otro que configuré fue un proxy transparente en nginx para tener un fácil soporte para https, ya que tengo un certificado wildcard mediante LetsEncrypt para que de esa forma pueda tener cualquier subdominio habilitado en mi casa: con un poco de magia en pfsense, otro poco de magia en CloudFlare y tener una pi siempre prendido y conectado esto se hace realmente fácil.
Una vez iniciadas las imágenes me puse a jugar un poco con algunos de los módulos tanto internos como externos que provee MagicMirror, hasta lograr el siguiente resultado:
Ahora ya había llegado la hora de dejar de probar cosas y sacar todo para pintarlo y dejarlo bonito, con todos los detalles hechos.
Sin embargo; antes de hacer eso; en un día particularmente flojo acá en la casa me puse a jugar con node-red para que hiciera unas cuantas cosas:
- Que prendiera la pantalla a las 8 de la mañana
- Que la apagara a las 21 horas
- Que la apague cuando detecte que ni yo ni mi señora están en la casa
- Que la prenda cuando detecte que llegamos de vuelta
Detectar si estamos en la casa:
Que envié el comando para apagar o prender la pantalla:
Detectar si estamos en la casa:
Enviar el comando para apagar o prender la pantalla:
Código:
[{"id":"4902c068.3cf1f8","type":"tab","label":"Empty house","disabled":false,"info":""},{"id":"4004dd79.2dc08c","type":"mqtt in","z":"4902c068.3cf1f8","name":"Is Camilo home","topic":"sensors/whoishome/camilo","qos":"2","datatype":"utf8","broker":"16d5a874.d300a","x":180,"y":220,"wires":[["bcd8b923.f03698"]]},{"id":"292dbc71.d14644","type":"mqtt in","z":"4902c068.3cf1f8","name":"Is Eva home","topic":"sensors/whoishome/eva","qos":"2","datatype":"utf8","broker":"16d5a874.d300a","x":170,"y":260,"wires":[["bcd8b923.f03698"]]},{"id":"bcd8b923.f03698","type":"join","z":"4902c068.3cf1f8","name":"Combine","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":",","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":340,"y":240,"wires":[["a0272ae3.d08f2","a30c524d.37227"]]},{"id":"f6ac59b3.ca7da8","type":"change","z":"4902c068.3cf1f8","name":"People not present","rules":[{"t":"set","p":"payload","pt":"msg","to":"no","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":260,"wires":[["eb7cead1.e670e","46b0392a.e202b"]]},{"id":"c8163e42.e92868","type":"change","z":"4902c068.3cf1f8","name":"People present","rules":[{"t":"set","p":"payload","pt":"msg","to":"yes","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":220,"wires":[["eb7cead1.e670e","46b0392a.e202b"]]},{"id":"eb7cead1.e670e","type":"mqtt out","z":"4902c068.3cf1f8","name":"MQTT Command out","topic":"sensors/whoishome/peoplepresent","qos":"0","retain":"true","broker":"16d5a874.d300a","x":1000,"y":200,"wires":[]},{"id":"46b0392a.e202b","type":"debug","z":"4902c068.3cf1f8","name":"People present in house?","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1010,"y":260,"wires":[]},{"id":"a0272ae3.d08f2","type":"debug","z":"4902c068.3cf1f8","name":"Combine output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":580,"y":360,"wires":[]},{"id":"3dc133bc.d0336c","type":"debug","z":"4902c068.3cf1f8","name":"Contains home","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":740,"y":180,"wires":[]},{"id":"a7719dea.fe04d8","type":"debug","z":"4902c068.3cf1f8","name":"Does not contain home","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":770,"y":300,"wires":[]},{"id":"2f49da32.6d874e","type":"inject","z":"4902c068.3cf1f8","name":"Test both not home => no","topic":"","payload":"[\"not_home\",\"not_home\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":140,"wires":[["a30c524d.37227"]]},{"id":"1cf631b7.2d92fe","type":"inject","z":"4902c068.3cf1f8","name":"Test one not home (1) => yes","topic":"","payload":"[\"not_home\",\"home\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":260,"y":60,"wires":[["a30c524d.37227"]]},{"id":"f20777b7.b6302","type":"inject","z":"4902c068.3cf1f8","name":"Test both home => yes","topic":"","payload":"[\"home\",\"home\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":180,"wires":[["a30c524d.37227"]]},{"id":"c308e095.980cc8","type":"inject","z":"4902c068.3cf1f8","name":"Test one not home (2) => yes","topic":"","payload":"[\"home\",\"not_home\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":260,"y":100,"wires":[["a30c524d.37227"]]},{"id":"a30c524d.37227","type":"function","z":"4902c068.3cf1f8","name":"find home","func":"const isHome = msg.payload.find(element => element == \"home\");\n\n//node.warn(isHome);\n//node.warn(msg.payload);\n\nif (isHome === undefined) {\n // If it does NOT contain home, use second output\n return [null, msg];\n}\n\n// If it does contain home, use first output\nreturn [msg, null];\n","outputs":2,"noerr":0,"x":520,"y":240,"wires":[["3dc133bc.d0336c","c8163e42.e92868"],["a7719dea.fe04d8","f6ac59b3.ca7da8"]]},{"id":"16d5a874.d300a","type":"mqtt-broker","z":"","name":"MQTT Broker","broker":"XXXXX","port":"1883","clientid":"nodered-mqtt-client","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]
Enviar el comando para apagar o prender la pantalla:
Código:
[{"id":"768a9d7d.7438a4","type":"tab","label":"MMonitor","disabled":false,"info":""},{"id":"ee7687e4.2319","type":"inject","z":"768a9d7d.7438a4","name":"Timed MM off","topic":"commands/mainmonitor","payload":"off","payloadType":"str","repeat":"","crontab":"00 21 * * *","once":false,"onceDelay":0.1,"x":513,"y":320,"wires":[["5178386f.558f78"]]},{"id":"7a814ebd.8d7ef","type":"inject","z":"768a9d7d.7438a4","name":"Timed MM on","topic":"commands/mainmonitor","payload":"on","payloadType":"str","repeat":"","crontab":"00 08 * * 1,2,3,4,5","once":false,"onceDelay":0.1,"x":513,"y":200,"wires":[["5178386f.558f78"]]},{"id":"8cf919e7.a48748","type":"switch","z":"768a9d7d.7438a4","name":"Anybody home?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"yes","vt":"str"},{"t":"eq","v":"no","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":300,"y":260,"wires":[["87e9d74a.953028"],["ffc8c1e1.0639f"]]},{"id":"5178386f.558f78","type":"mqtt out","z":"768a9d7d.7438a4","name":"MQTT Command out","topic":"commands/mainmonitor","qos":"0","retain":"true","broker":"16d5a874.d300a","x":760,"y":260,"wires":[]},{"id":"87e9d74a.953028","type":"change","z":"768a9d7d.7438a4","name":"MM on","rules":[{"t":"set","p":"payload","pt":"msg","to":"on","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":493,"y":240,"wires":[["5178386f.558f78","21428ff.87136f"]]},{"id":"ffc8c1e1.0639f","type":"change","z":"768a9d7d.7438a4","name":"MM off","rules":[{"t":"set","p":"payload","pt":"msg","to":"off","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":493,"y":280,"wires":[["5178386f.558f78","21428ff.87136f"]]},{"id":"21428ff.87136f","type":"debug","z":"768a9d7d.7438a4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":710,"y":320,"wires":[]},{"id":"2ee07f48.c203f8","type":"mqtt in","z":"768a9d7d.7438a4","name":"People present","topic":"sensors/whoishome/peoplepresent","qos":"2","datatype":"auto","broker":"16d5a874.d300a","x":100,"y":260,"wires":[["8cf919e7.a48748"]]},{"id":"16d5a874.d300a","type":"mqtt-broker","z":"","name":"MQTT Broker","broker":"XXXXX","port":"1883","clientid":"nodered-mqtt-client","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]
El script que efectivamente prende o apaga la pantalla es bastante simple: se subscribe a un tópico en particular, en este caso commands/mainmonitor y ejecuta lo siguiente cuando entra un mensaje a ese tópico:
Código:
# Para apagarlo: (mensaje "off")
/usr/bin/vcgencmd display_power 0
# Para prenderlo: (mensaje "on")
/usr/bin/vcgencmd display_power 1
Con respecto a la forma en que se hacía en una rpi3 se simplificó bastante, lo cual se agradece harto!
Volviendo al monitor, llegó la hora de los toques finales. Lo desarmé nuevamente para que mi señora eligiera el color y lo pintara acorde:
Finalmente llegó la hora de instalar la cámara (soldé los cables sueltos al conector USB) la cual cupo bastante bien:
Y así es el resultado en el frontis:
Finalmente, le apliqué un poco de pegamento y lo dejé secando:
Una vez seco, llegó la hora de hacer la instalación final! Ya era de noche pq no me pude aguantar así que disculpen la calidad de las fotos, pero aquí van:
Y de cerca:
Y eso sería todo amigos! Cualquier duda o consulta, por favor háganla!
Saludos.
Última modificación por un moderador: