Evaristo R

Técnico Administrador de Sistemas

Piloto de RPAS certificado por AESA/EASA - A1/A3 - A2 - STS

Profesor e-Learning

Evaristo R

Técnico Administrador de Sistemas

Piloto de RPAS certificado por AESA/EASA - A1/A3 - A2 - STS

Profesor e-Learning

Blog Post

Busylight – Un semáforo para teletrabajar – DIY

2 septiembre, 2024 General, Raspberry
Busylight – Un semáforo para teletrabajar – DIY

Directo al grano: https://github.com/evaristorivi/busylight-evaristorivi

Introducción

Trabajar desde casa es lo mejor, pero a veces pueden darse situaciones incómodas, cuando estamos en una reunión, y un familiar entra sin avisar en el despacho, hablando fuerte. En el mejor de los casos no tendremos la WebCam activada; y el cancelador de ruido será nuestro salvador, pero pueden darse situaciones incómodas, sobre todo en reuniones importantes. En mi casa, por ejemplo teletrabajamos tanto mi pareja como yo, y es muy habitual que de vez en cuando nos hagamos una visita entre despachos, pero no siempre puede ser el mejor momento para ello, si el otro está en una llamada espontánea.

Así que en una de estas que casi salgo en calzoncillos en algun escritorio del otro lado del mundo, se me ocurrió ¿Y si hago un semáforo, algo sencillo… una API que corra en la Raspberry, y los equipos se comuniquen con él? Todo en Python… mmm sonaba tentador.. pero ¿Alguien lo habrá hecho ya?

Encontré el blog de Elio Struyf y la versión de carolinedunn, proyectos ambos muy interesantes que me sirvieron de inspiración y punto de partida, pero , estaban pensados para funcionar con el estado de Teams. ¿Bueno y ¿Cuál es el problema?

  1. Mi pareja trabaja con macOS y en Slack.
  2. Yo si trabajo con Teams, pero no tengo los permisos necesarios a nivel corporativo para configurar una App en Azure para poder obtener en tiempo real el estado de mi Teams.

Además, estos proyectos son solo para un despacho, pero ¿Y si somos una pareja con dos despachos?

Seguí investigando, y en este hilo encontré una muy buena idea:

(…) Then I just use the HA macOS app to detect when mic or camera is in use (…)

87racer

¡Et voilà! No me apetecía depender de HA (Home Assistant) ¿Y si hacía mi propia API – Busylight, con la que se comunicarían los equipos mediante un script en Python que detectase el uso del micrófono? ¡Hacía tiempo que quería trastear con FastAPI, y esta era la oportunidad perfecta!

Así que a continuación voy a describir mi proyecto por si te apetece intentarlo, paso a paso para que quede así:

Busylight – Lista de la compra del Hardware:

Lo primero, ¿Qué necesitamos comprar?

Raspberry Pi Zero 2W

También podrías comprar el modelo anterior, en concreto el modelo anterior WH viene ya con los conectores soldados (Aunque no te preocupes por eso porque luego veremos las opciones sin soldadura), es una buena opción, aunque perderías en núcleos del procesador. Yo compré esta:

https://amzn.to/4cQQ0Sl

Por supuesto no te olvides de poner una buena MicroSD, te aconsejo pasarte por aquí si no sabrías ahora mismo cual poner:

Fuente de alimentación oficial:

https://www.tiendatec.es/raspberry-pi/raspberry-pi-alimentacion/1734-alimentador-oficial-raspberry-pi-micro-usb-5-1v-2-5a-12-75w-0617588405747.html

Waveshare  Raspberry Pi RGB LED HAT 4×8

https://es.aliexpress.com/item/1005007512902721.html?spm=a2g0o.order_list.order_list_main.10.6ba4194dKQUMME&gatewayAdapt=glo2esp

Viene con unos tornillos y tuercas que NO usaremos.

Documentación oficial: https://www.waveshare.com/wiki/RGB_LED_HAT

pHat Diffuser

Este es un difusor de luz para los leds, tienes la opción de comprarlo:

https://www.kiwi-electronics.com/en/phat-diffuser-3210?srsltid=AfmBOopLW6U7Ncj2_TersmpoeC2FP38t3TlFL1NZ8NOg-xiQMpbaykni

o bien imprimirlo si tienes impresora 3D, con material PETG Transparente, que es lo que hice yo. Te dejo también el link a mi thingiverse: https://www.thingiverse.com/thing:6747513

La razón de que sea PETG y no PLA es que el PLA se podría deformar con temperaturas de 60º y aunque la idea es no llegar a estas temperaturas, con el PETG habría margen de sobra hasta los 80º, por lo que es ideal además de ofrecer mejor translucidez.

Material PETG clear que yo uso: https://amzn.to/3YPcJe3 

Conexiones: Con Soldadura / Sin Soldadura

Si viste el blog de Elio Struyf, te habrás fijado que usa lo siguiente: POGO-A-GO-GO PINES POGO SIN SOLDADURA, pero estos no serán compatibles con la Waveshare – RGB LED pHAT (4 x 8) ya que tiene el cabezal hembra ya instalado, a diferencia de la mayoría de proyectos de hace unos años que usaban Pimoroni Unicorn pHa, que si tenía los conectores libres. así que esto solo te lo recomiendo si compraste una Pimoroni Unicorn pHa, si estás siguiendo esta guía entonces esto NO HAY QUE COMPRARLO.

Entonces, o bien tienes una Raspberry que trae ya presoldado el cabezal macho, o necesitas una solución para esto. Yo te propongo varias:

Sin Soldadura

Kit montaje tira de contactos hembra + macho sin soldadura

https://www.tiendatec.es/raspberry-pi/gpio/1058-kit-montaje-tira-de-contactos-hembra-macho-sin-soldadura-704256800581.html

¡Con unos ligeros golpes ya lo tendríamos!

También se vende más barato sin el kit:

https://www.tiendatec.es/raspberry-pi/gpio/794-tira-de-2×20-pines-macho-sin-soldadura-704256800598.html

Cables Dupont

https://amzn.to/3Z5bwiY 

Así empecé yo a probarlo, ya que me sobraban algunos cables Dupont. Puedes ver en la imagen como se conectan, en los pines 2, 6 y 12 tal y como indica la documentación, 5V, GND, y DIN: https://www.waveshare.com/wiki/RGB_LED_HAT

Con Soldadura

Tira de 2×20 pines coloreada para raspberry pi

https://www.tiendatec.es/raspberry-pi/gpio/1071-tira-pines-2×20-coloreada-para-raspberry-pi-8472496015684.html

Es la que finalmente compré yo, ya que quería que quedase compacto, y me gusta soldar; aunque si no tienes experiencia te recomendaría la opción de Kit montaje tira de contactos hembra + macho sin soldadura.

Tira de 2×20 pines macho

Por si no te molan los colorines, pero es lo mismo:

https://www.tiendatec.es/electronica/accesorios/297-tira-de-2×20-pines-macho-8402970760014.html

El proceso de soldar no fue extremadamente complicado, pero si necesitarás un soldador de punta fina. En mi caso con el Jbc 40ST26W con punta de 1.0mm de diametro + Flux se me dio bien aunque hubiera agradecido una punta más fina. Este es el proceso y resultado de la opción con soldadura:

y ya para finalizar la lista, lo que será la estructura:

Caja de acrílico transparente para Raspberry Pi Zero

https://es.aliexpress.com/item/1005005790632260.html?spm=a2g0o.order_list.order_list_main.5.6ba4194dKQUMME&gatewayAdapt=glo2esp

Yo compré esta que en realidad es para el modelo anterior de Raspberry Pi Zero. No pasa nada ya que solo me interesaba la parte de abajo, y la tornilleria. Lo que cambia es sobre todo la parte de arriba que es diferente para la versión 2W, pero no lo usaremos.

Esta tornilleria es la que use para el montaje pero de manera distinta a como se supone que había que hacerlo. En lugar de colocar los cuatro tornillos iguales, ya que nosotros meteremos el difusor LED por encima, usaremos dos tornillos por un lado para sujetar el difusor led y otros dos tornillos en el lado contrario para la base, quedando así:

Primeras pruebas con la Waveshare RGB LED HAT 4×8

¡Una vez instalado Raspberry Pi OS (Con Raspberry Pi Imager), nos ponemos a hacer las pruebas!

A continuación he creado un script, basado en la documentación oficial pero refinado, para que funcione perfecto nada más lo pruebes:

# Creamos la carpeta del proyecto y el entorno
# Tenemos que ser root, pues rpi_ws281x va conectarse con nuestro hardware directamente.
sudo -i 
mkdir busylight
cd busylight/

# Creamos el entorno virtual
python3 -m venv busylight-env
source busylight-env/bin/activate

# Instalamos rpi_ws281x para comunicarnos con los leds
pip install rpi_ws281x

# Primera prueba básica
wget https://files.waveshare.com/upload/b/b2/RGB_LED_HAT.zip
unzip RGB_LED_HAT.zip
cd RGB_LED_HAT
python3 ws2812.py
#FUNCIONA! 

Por supuesto esto es compatible con cualquier Raspberry, funcionará tal cual si lo instalas en una Raspberry Pi3 por ejemplo:

Si te funcionó esto, ahora podemos hacer otra prueba, controlando los colores con un móvil en la red wifi:

pip install bottle
cd web
python3 main.py

en este momento te dirá «Listening on http://la.ip.de.tu.raspberry:8000/» por lo que es buen momento para acceder con el móvil dentro de la red wifi.

¿Apagamos nuestra prueba?

podemos instalar ipython para seguir jugando,

pip install ipython
ipython

y ya dentro pegar lo siguiente:

from rpi_ws281x import Adafruit_NeoPixel, Color
# LED strip configuration:
LED_COUNT      = 32      # Number of LED pixels.
LED_PIN        = 18      # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 10      # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 255     # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)

# Create NeoPixel object with appropriate configuration.
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)

# Initialize the library (must be called once before other functions).
strip.begin()
def turn_off_leds():

    """Set all LEDs to 'off' by turning them black (Color(0, 0, 0))."""

    for i in range(strip.numPixels()):

        strip.setPixelColor(i, Color(0, 0, 0))

    strip.show()

# Turn off all LEDs
turn_off_leds()

print("LEDs off.")

Busylight – evaristorivi – API y Scripts cliente:

¡Bien ya probamos todo! y..¡Funciona!

Pues ahora vamos a implantar el proyecto. Puedes encontralo en Github, con documentación detallada para instalar tanto la API como los scripts cliente. Esta es la url:

https://github.com/evaristorivi/busylight-evaristorivi

Para no repetir info, en esta entrada de blog vamos a ver como se configura y el funcionamiento:

Componentes del Proyecto

  • Servidor API: Un servicio de backend que recibe señales y controla la luz.
  • Cliente Windows: Un script en Python para Windows (10 y 11) que monitoriza el estado del micrófono y se comunica con el servidor API.
  • Cliente macOS (NEW): Un script en Python para sistemas macOS modernos que monitoriza el micrófono y envía señales a la API, probado en Sonoma.
  • Cliente macOS (LEGACY): Un script en Python para sistemas macOS Legacy que monitoriza el micrófono y envía señales a la API, probado en El Capitan.
  • Script de Apagado (Opcional): Un script que apaga la luz a través de la API. si quieres, puedes programar un evento que se active al cerrar sesión y lance este script de apagado. Es totalmente opcional ya que el mismo servidor API apagará los LEDs automáticamente cuando finalice el horario de operaciones programado.

El Servidor API

El servidor API está parametrizado de la siguiente manera:

Se establece una intensidad por defecto, que será la que aplicará en el caso de que CONTROL_INTENSITY sea False o en el caso de que sea True, y los clientes especifiquen este valor de intensidad en la llamada.

A la hora de la verdad, los scripts clientes no la especifican, pero es muy útil para mandar señales manualmente testando qué intensidad es mejor según la ubicación donde tengamos pensado instalar el semáforo.

Por otro lado vemos la configuración de «Programación» en el que en este momento está activado y programado de Lunes a Viernes de 8:00 a 18:00. Fuera de este horario el servidor no encenderá luces y si las tenía encendidas las apagará.

INVERT_POSITION nos ayudará con la instalación en la puerta, por si decidimos instalarlo con el cable de carga hacia arriba en lugar de hacia abajo.

Documentación de la API:

Podemos navegar a las siguientes direcciones para ver la documentación interactiva:

Documentación: http://Tu.API.IP…:5000/docs

En concreto en /docs podremos lanzar señales desde la web para testear:

Por ejemplo aquí estamos indicando que se ilumine de rojo, la zona izquierda con intensidad 20%

Podríamos haber indicado también:

{
  "color": "red",
  "half": "left",
}

O para iluminar todo de rojo, simulando que somos los únicos que teletrabajamos en casa:

{
  "color": "red"
}

Podríamos subirle hasta un 100% de intensidad, si tenemos desbloqueada esta opción en la API, pero según mis pruebas, 20% consigue mantener una temperatura estable en todo momento, por debajo de 50 grados, y además no molesta la luz a los ojos.

Luego está la opción para apagar:

De esta manera apagaríamos todo, y de la siguiente solo el lado izquierdo:

{
  "half": "left"
}
Si accedemos a http://Tu.API.IP…:5000/redoc

Veremos la documentación en otro formato, pero aquí no podremos interactuar.

Instalación de la API

Hice un script de instalación que configurará el entorno virtual con sus dependencias además de crearte el servicio systemd para el arranque del servidor uvicorn. Lo encontrarás en

api-BusyLight/install.sh

Scripts cliente

Los scripts cliente están parametrizados de la siguiente manera:

Configuramos la dirección donde estará nuestro servidor API (Convendría que fuera una IP fija ya sea por configuración en el Sever o por DHCP en el router, como es mi caso)

Configuramos si usaremos el script en modo compartido con otro despacho o no. En el caso de que sí, indicaremos que lado seremos nosotros, Rigt o Left. En el caso que no, dará igual que tengamos configurado en SHARED_SIDE ya que no se usará, y los led brillarán al completo.

¿Cómo detecto el uso del micrófono?

macOS Legacy

En macOS Legacy utilizo una solución muy limpia y sencilla, con ioreg‘, ‘-l’, ‘AppleHDAEngineInput’:

def is_microphone_in_use():
    try:
        # Execute the ioreg command to get the input details of the AppleHDA engine
        result = subprocess.run(
            ['ioreg', '-l', 'AppleHDAEngineInput'],
            capture_output=True,
            text=False
        )
        
        # Use grep to filter the output based on IOAudioEngineState being active (1)
        grep_result = subprocess.run(
            ['grep', '"IOAudioEngineState" = 1'],
            input=result.stdout,
            capture_output=True,
            text=False
        )

        # Count the number of matching lines with wc -l
        wc_result = subprocess.run(
            ['wc', '-l'],
            input=grep_result.stdout,
            capture_output=True,
            text=False
        )

En El Capitan, cuando no se está usando el micrófono veremos todo a 0

Pero en cuanto se usa un micrófono veremos al menos un 1

Una solución que me encanta. Una pena que no valga para versiones más modernas de macOS en donde he tenido que improvisar con atomacos.

macOS modernos (Sonoma)

Aquí utilizo atomacos que es una librería de Python que permite acceder y manipular elementos de la interfaz gráfica de macOS. Actúa como un puente entre el script y las herramientas visuales del sistema operativo.

Primero, obtengo una referencia al Control Center de macOS, donde se gestionan varias configuraciones del sistema, incluyendo el estado del micrófono, y utilizo atomacos.getAppRefByBundleId(‘com.apple.controlcenter’) para conectar con esta parte del sistema. Con la referencia al Control Center, busco elementos específicos que indiquen el uso del micrófono, verificando atributos como AXDescription en los elementos de la interfaz y filtro estos elementos para encontrar aquellos que mencionen «Microphone» y «use».

Windows 10/11

En Windows se complica un poco más la cosa, y he tenido que tirar de pycaw que es una biblioteca de Python que permite interactuar directamente con el sistema de audio de Windows a través de la Core Audio API. Con pycaw, obtengo todas las sesiones de audio activas usando AudioUtilities.GetAllSessions() que me da una lista completa de las sesiones en curso. Por cada sesión, consulto su estado con la interfaz IAudioSessionControl2 lo que me permite saber si la sesión está activa o no. Luego analizo qué aplicación está utilizando cada sesión, y comparo el nombre del proceso con una lista de aplicaciones conocidas por usar el micrófono, como Teams, Zoom, o Skype (y las que quieras añadir) y con otra de procesos a ignorar, por si en algún momento alguno produce falso positivos, como me pasó con simhubwpf.exe un proceso que captura telemetría en videojuegos de simulación.

GNU-Linux

En próximos días también estará disponible en el repo la versión del cliente para Linux, ya que tenía que sacar tiempo para probarlo en todas las distros. En Linux es más limpio aún que en Mac Legacy ya que primero se detecta qué sistema de audio está en uso (PulseAudio, PipeWire o ALSA) y luego según el resultado se usa un comando u otro. Para PulseAudio y PipeWire se detecta con pactl y para ALSA con arecord.

Por ejemplo en Raspberry Pi OS, con:

 pactl list source-outputs

obtendríamos, nada si no hay ningún micrófono en uso y lo siguiente si estuviese siendo usado un micrófono:

La conclusión al final es que los métodos utilizados en macOS y en GNU-Linux son los mejores y siempre funcionarán con cualquier aplicación. En Windows hay que filtrar por nombre de proceso y por ejemplo Chrome creará falsos positivos al reproducir vídeos en Youtube, por la manera en la que detectamos las sesiones de audio. Desconozco si existirá ahora o más adelante un método mejor para detectarlo.

Ejemplos de uso

Windows: Ejemplo de uso

Para Windows hice un instalador, que se encargará de instalar el entorno virtual, dependencias y la tara programada para que nada más iniciar sesión tengamos el cliente monitorizando nuestro estado. Lo encontrarás en:

client-scripts/windows/install.ps1

Cuando finalice su instalación se arrancará automáticamente la tarea por lo que comenzará a funcionar, y a partir de este momento lo tendremos automatizado para que se lance en cada inicio de sesión. Mientras que estemos libres emitirá señal «verde» y cuanto nos llamen por nuestra aplicación de comunicaciones, por ejemplo Teams, mandará señal de «ocupado» al servidor API.

La ventana del cliente saldrá minimizada por defecto y no deberíamos de cerrarla; me hubiera gustado mantenerla oculta, como lo hice en macOS, pero solo se me ocurre hacer esto posible usando VB, y preferí mantenerlo simple.

macOS Ejemplo de uso

Primero instalaremos Python si no lo tenemos ya y las dependencias necesarias con:

git clone https://github.com/evaristorivi/busylight-evaristorivi
cd busylight-evaristorivi/client-scripts/macOS/NEWS/
# or
cd busylight-evaristorivi/client-scripts/macOS/LEGACY/
pip install -r requirements.txt # if you get error: externally-managed-environment see how to create virtual environment before in the gnu-linux section. It is the same for macOS.
python3 mic-in-use_macOS-News.py
# or
python3 mic-in-use-macOS-Legacy.py

busylight-evaristorivi/client-scripts/macOS/NEWS es para macOS actuales como sonoma.

busylight-evaristorivi/client-scripts/macOS/LEGACY ha sido probado en El Capitan.

Esto es lo que se vería si lo ejecutasemos con el IDLE.

Nota: «Como lo vamos a automatizar con Automator en el próximo apartado, cuando inicies el Mac, no verás ninguna ventana, y en background la App se comunicará en silencio con nuestra API.»

¿Cómo automatizar en Mac?

En Mac deberás de ser tú el que automatice con Automations. Te dejo los pasos:

Abres Automator y seleccionas crear un Application

Tienes que averiguar la ruta de python pero cuidado, desde root que si no te dará una distinta con tu usuario:

which python3

Entonces seleccionar Run Shell Script y pones la ruta de python3 seguido de la ruta al script (Puedes arrastrar el script aquí y hará la magia de autocompletar la ruta completa)..

Dale a Run y si te pide permisos se los das.

Listo, tras esto puedes guardarlo en un directorio, y luego ir a «Login Items» en preferencias y añadir la .app que acabamos de crear.

macOS Sonoma:

En Sonoma siempre verás un icono giratorio en la barra superior, pero no es molesto.

macOS El Capitan: Lo bueno de El Capitán es que puedes ocultar la tarea.

¡Reiniciamos y veremos como se enciende nuestra luz verde (o roja si justo nos están llamando! 😀

También se puede hacer con .plist de LaunchDaemons o LaunchAgent pero como me funcionó así no lo probé de más maneras.

GNU-Linux Ejemplo de uso

Con lo siguiente tendrías funcionando el cliente:

git clone https://github.com/evaristorivi/busylight-evaristorivi
cd busylight-evaristorivi/client-scripts/gnu-linux/
# creation of the virtual environment, because in the latest python updates without this it will give error: externally-managed-environment
mkdir -p venv
python3 -m venv venv
venv/bin/pip install --upgrade pip
venv/bin/pip install -r requirements.txt
venv/bin/python3 mic-in-use-gnu-linux.py

Eso sí, la parte de automatizar el script al inicio del sistema, te la dejo a tu elección según que uses (GNOME o Mate, Xfce…) ya que podría ser ligeramente diferente.

Script de Apagado (Opcional):

Un simple script que lanza una señal de apagado.

Sería útil si haces una tarea programada en Windows y/o macOS que capture el estado de cerrar sesión y lo lance… A mi me dio pereza hacer esto último, y como al final el servidor API apagará todas las luces cuando llegue la hora de fin de operaciones, no me preocupé en implantarlo, pero bueno ahí queda como una posibilidad más.

Conclusión

Esto ha sido todo, por supuesto si tienes alguna duda puedes dejarla en los comentarios. Voy a volver a dejar por aquí el enlace al Github para que no tengas que volver a buscarlo por arriba:

https://github.com/evaristorivi/busylight-evaristorivi

Así quedó con un poco de cinta de doble cara: https://amzn.to/4ec38lP

Si has impreso la pieza difusora, con una se verán los puntitos de los leds, y con dos superpuestas obtendrás una luz totalmente difusa, perfecto si modificas la pieza externa para poner unas iniciales por ejemplo.

Nosotros lo vamos a usar a diario, tanto para saber entre cuando podemos molestarnos, como para cuando tengamos visita, y sepan ellos cuando sí y cuando no deberían de entrar en los despachos.

Taggs:
6 Comments
  • Mati 8:12 am 3 septiembre, 2024 Responder

    ¡¡Enhorabuena!!

  • Ana Rosiris 3:02 pm 4 septiembre, 2024 Responder

    Guao!!! eres más que creativo y organizado. También eres un crack proactivo. Felicidades por tu cumpleaños y tu proyecto.

    • Evaristo R 4:40 pm 4 septiembre, 2024 Responder

      ¡Muchas gracias Ana! ¡Todo lo bueno se pega! 😀

  • Chandru 2:55 pm 9 septiembre, 2024 Responder

    Impressive!

Write a comment

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información. Si está interesado en leer el aviso de privacidad pinche aquí.

ACEPTAR
Aviso de cookies