This entry is part 2 of 2 in the series Detección de movimientos con IA como control domótico

Detección de movimientos con IA como control domótico

Integración de sistemas con IA generativa: Detección de movimientos como control domótico

Detección de movimientos con IA como control domótico. Configuración maestro/esclavo a más de 600 kilómetros de distancia

Comentaba en el artículo anterior que la primera evolución obvia del proyecto era pasar de un modelo local, en el que el sensor Kinect está conectado al servidor donde se realiza el procesamiento, a un modelo maestro/esclavo, donde el sensor está en un equipo sencillo, y la lógica de procesamiento se encuentra en un servidor remoto, con la potencia de cálculo adecuada como para realizar de manera adecuada la detección de movimientos y la integración con el sistema de domótica.

Imagen conceptual de la arquitectura. Creada con IA

La razón, en mi caso, para realizar esta evolución es que todo el sistema, en su configuración local, se encuentra en una máquina virtual desplegada en mi servidor ProLiant DL360p Gen8. No es exactamente la clase de decoración que puedas colocar en el salón de tu casa. Pero, más allá de este caso concreto, es razonable tender a este modelo de trabajo, sobre todo si pensamos en aplicaciones prácticas en entornos de tipo industrial, donde es conveniente estructurar los modelos de despliegue de acuerdo al modelo Purdue, con segmentación entre la red de sensores y la red de procesamiento. Algo que se hace no sólo por motivos de eficiencia (mejor desplegar múltiples sensores baratos y resistentes en un entorno naturalmente agresivo que trabajan contra un servidor central, que no tener sensores caros en el entorno), sino también de seguridad, estableciendo redes separadas, control de tráfico entre ellas y, sobre todo, reduciendo superficie de ataque mediante la limitación de servicios desplegados en la red industrial.

Servidor ProLiant DL360p Gen8. No es exactamente lo que quieres tener en el salón de tu casa

Hay una segunda razón para que me interesara trabajar en este modelo maestro/esclavo, y es la siguiente: aunque en un primer lugar había pensado este modo de trabajo para el salón de mi casa y la sala donde tengo el servidor, al final lo he evolucionado a un modelo de larga distancia: ¿sería un modelo de trabajo factible para un entorno en el que el servidor está en Sevilla, y el sensor Kinect en Pontevedra, con una separación en línea recta de más de 600 kilómetros? Y esta pregunta también merecía una respuesta.

Llegados a este punto, era cuestión de empezar a analizar:

  • Configuración ROS maestro/esclavo: El primero de los aspectos a tratar. ¿Era posible que ROS funcionara en una configuración maestro/esclavo? Y la respuesta era sí. Es, de hecho, una configuración bastante común. La idea subyacente es que existe un nodo ROS, configuración en función de maestro, que actúa como elemento principal de la arquitectura, y uno o varios nodos esclavos, que a la hora de publicar sus topics lo hacen en el servidor maestro.
  • Distribución de funciones entre maestro y esclavo: El segundo de los aspectos era el de la distribución de la lógica. En la configuración local, en líneas generales, se realizaban dos acciones: reconocer y procesar la información proveniente del sensor Kinect, publicándola en un topic ROS para que pudiera ser consumida; y el procesamiento mediante Machine Learning de la secuencia de vídeo por parte de MediaPipe, realizando el procesamiento esquelético, reconocimiento de gestos, y publicación en MQTT para control domótico. La primera de las acciones tiene una carga de trabajo limitada, mientras que la segunda es intensiva en capacidad de cómputo. Era obvio que lo adecuado era dejar la primera acción para el nodo esclavo y la segunda para el maestro. Esto permitía, además, que el nodo esclavo fuera ligero y con unas necesidades de cómputo livianas, lo que permitía el uso de sistemas muy compactos. En cuanto al segundo, al estar ya libre de sensórica física, lo habilitaba para ser desplegado en cualquier ubicación y con cualquier potencia de cálculo.
  • Elección del nodo cliente: Aquí mi idea preliminar era usar un hardware de bajo consumo. Idealmente una Raspberry Pi 4, pero lo que tenía a mi disposición era una Orange Pi Zero. Un equipo con procesador ARM H3 Quad-core Cortex-A7 y 512 MB de RAM. Limitado, pero sumamente compacto. Y, sobre el papel, válido para esta idea. Sin embargo, y por razones que detallaré más adelante, acabaría usando una máquina virtual en un portátil ThinkPad X270.
  • Conectividad entre maestro y esclavo: El aspecto clave. Cómo conectar entre sí los nodos. Y aquí valía la pena aplicar dos conceptos muy interesantes para una red industrial. Redes definidas por software (SDN) y ZTNA. Dado que ambos nodos se encontraban en redes completamente separadas (el maestro en una red local en Sevilla, y el esclavo en una red virtual en un portátil, tras un NAT), me decidí a crear una red definida por software entre ambos nodos, lo que técnicamente correspondería a una red SD-WAN. Desde el punto de vista de los nodos estarían en una red local, pero desde el punto de vista de la infraestructura de red estarían en sus sistemas separados, y sin necesidad de abrir puertos, realizar enrutados, o cualquier otra clase de procesado del tráfico de manera convencional. Además, el método escogido me permitía implementar los principios de ZTNA (Zero Trust Network Access), ya que podía implementar un control granular de los sistemas conectados a dicha red, mediante conectividad segura (las interfaces virtuales, en realidad, se realizan como túneles cifrados extremo a extremo), control de acceso (con permisos individualizados a nivel de dispositivo) y visibilidad de los dispositivos conectados. La manera de implementarlo fue mediante ZeroTier, un sistema que vengo usando desde hace tiempo para interconectar mis sistemas.

Con todo esto en mente, el diagrama del sistema quedaría como sigue:

Cambios en la configuración del nodo maestro

El nodo maestro, pues, sería la evolución del nodo local que había preparado en la fase anterior del proyecto. Recordemos:

  • Máquina virtual desplegada en mi servidor ProxMox, con el sensor Kinect conectado a un puerto USB local, y mapeado a la máquina virtual.
  • Un servicio, freenect.launch.service, que se encargaba de conectar el sensor Kinect mediante freenect y OpenNi, para inyectar las imágenes en ROS.
  • Otro servicio, mediapipe_pose.service, que se conectaba al topic ROS adecuado, procesaba la imagen, obtenía la imagen esquelética, procesaba gestos, e inyectaba en el servidor MQTT.

Los cambios a realizar en este nodo fueron los siguientes:

  • Instalar el cliente ZeroTier, para crear una IP virtual para el servidor.
  • Deshabilitar freenect.launch.service, que ya no prestaba ninguna función.
  • Definir las variables ROS_MASTER_URI y ROS_IP. Estas variables le dicen al sistema ROS que va a trabajar en una configuración maestro/esclavo. Como este nodo iba a ser el nodo maestros, ambas variables apuntarían a su propia dirección IP, en este caso, la de la red SD-WAN desplegada con ZeroTier. En un primer momento definí las variables a nivel de sistema; una vez probado el correcto funcionamiento, pasé a definirlas en los servicios systemd apropiados.
  • Crear un nuevo servicio roscore.service. Dado que el servicio ROS anteriormente se levantaba dentro del servicio freenect.launch.service, era necesario crear un nuevo servicio simplificado que se encargara de levantar roscore.
  • Modificar el servicio mediapipe_pose.service para que contemplara las variables de entorno anteriormente mencionadas, y que dependiera de roscore.service, para asegurar la correcta puesta en marcha de los servicios.

La belleza esta configuración es que no era necesario realizar ningún tipo de cambio en la lógica de MediaPipe, ya que desde un inicio se apoyaba en un topic ROS, que seguía siendo el mismo, aun siendo publicado por el nodo esclavo.

Instalación del nodo esclavo

En cuanto al nodo esclavo, en líneas generales el trabajo implicaba desplegar sistema operativo, freenect, ROS y OpenNi, y conectar el sensor Kinect. Mi primer impulso había sido usar una Raspberry Pi 4, pero no tenía ninguna disponible. Mi segunda opción era la Orange Pi Zero, pero me encontré con un problema bastante importante.

Orange Pi Zero con su carcasa oficial. Muy compacta, pero con problemas de sobrecalentamiento

El problema no era otro que el de los sistemas operativos disponibles. Esta placa es bastante antigua, y admite el despliegue de Ubuntu 16.04, Debian Stretch, y Android 4. Nada de ello valía para desplegar ROS Noetic. Aún así, estuve haciendo algunas pruebas, para ver si era posible solventarlo de alguna manera, pero acabé por rendirme a la evidencia. Tocaba descartar la Orange Pi Zero.

El Mac Mini M4, con su arquitectura Apple, tampoco es adecuado para el despliegue de un entorno Ubuntu 20.04, así que, aprovechando que hacía poco me había hecho con un ThinkPad X270 al que le había instalado una Debian 13, aproveché para instalar KVM-Qemu, y desplegar una Ubuntu 20.04.

ThinkPad X270 de 13». Equipo duro donde los haya…

A partir de aquí era simplemente seguir las instrucciones del artículo anterior para desplegar ROS Noetic, los drivers Freenect para Kinect, y OpenNi para poder inyectar la imagen del sensor Kinect en un topic ROS.

El único aspecto a tener en cuenta era el del trabajo en modo maestro/esclavo. En este caso, el ROS desplegado en este nodo pasaba a ser el esclavo. Era preciso, por ello, hacérselo saber. Y como en el caso del nodo maestro, se hacía definiendo las variables ROS_MASTER_URI y ROS_IP. En este caso, la URL de ROS_MASTER_URI tendría que ser la del servidor maestro, y ROS_IP la IP del nodo esclavo.

Por último, quedaba lanzar de manera automatizada el servicio. Sin embargo, aquí introduje una pequeña variación: dado que el ROS esclavo necesita que el ROS maestro esté funcionando para poderse lanzar adecuadamente, en vez de crear un servicio que simplemente lanzara el ROS esclavo, creé con ayuda de Perplexity un script que primero comprueba si el ROS maestro está activo y, si lo está, entonces lanza el servicio. El servicio systemd lanza este script.

Esta es la lógica de start_freenect.sh:

#!/bin/bash
# start_freenect.sh - ros-remote (10.147.xxx.160)

MASTER_URI="http://10.147.xxx.205:11311"
MASTER_IP="10.147.xxx.205"

export ROS_MASTER_URI=$MASTER_URI
export ROS_IP=10.147.xxx.160

# Cargar entorno ROS
source /opt/ros/noetic/setup.bash
source /home/username/catkin_ws/devel/setup.bash 2>/dev/null || true

echo "[start_freenect] Comprobando ROS master en $MASTER_URI..."

# Esperar a que el master esté disponible (máx ~30s)
MAX_TRIES=30
i=0
while [ $i -lt $MAX_TRIES ]; do
    if curl -s "$MASTER_URI" | grep -q "Unsupported method"; then
        echo "[start_freenect] ROS master disponible."
        break
    fi
    echo "[start_freenect] ROS master no responde, reintentando... ($((i+1))/$MAX_TRIES)"
    sleep 1
    i=$((i+1))
done

if [ $i -ge $MAX_TRIES ]; then
    echo "[start_freenect] ERROR: ROS master no disponible tras $MAX_TRIES intentos. Saliendo."
    exit 1
fi

echo "[start_freenect] Lanzando freenect_launch..."
exec roslaunch freenect_launch freenect.launch depth_registration:=true

…y este el servicio systemd freenect.service:

[Unit]
Description=Kinect v1 freenect_launch (ROS Noetic)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=jhidalgo
Environment="HOME=/home/username"
# Variables ROS maestro–esclavo
Environment="ROS_MASTER_URI=http://10.147.xxx.205:11311"
Environment="ROS_IP=10.147.xxx.160"
ExecStart=/bin/bash /home/username/start_freenect.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/home/username

[Install]
WantedBy=multi-user.target

Integración en el sistema domótico: Home Assistant

Uno de los puntos que había quedado por definir en el artículo anterior era la integración efectiva en el sistema domótico. No había avanzado en ello ya que, habiendo llegado al punto de inyectar las acciones en un topic MQTT, el procesado de las acciones era algo trivial. Sin embargo, para hacer una verdadera prueba funcional, era algo necesario de implementar.

Interfaz de usuario de Home Assistant

En mi caso, mi sistema domótico se basa en una instalación local de Home Assistant. Decidí que, ya que me encontraba en Forcarey, debía interactuar con las luces de aquí. Estas luces se gestionan mediante sistemas Sonoff con firmware Tasmota, que son reconocidos en Home Assistant como switches. Por tanto, para utilizar el gesto de levantar el brazo derecho para encender/apagar la luz, bastaba con una automatización como la que sigue:

- alias: "Salón Forcarey - Toggle con gesto brazo"
  trigger:
    - platform: mqtt
      topic: "domotica/brazo_presencia"
      payload: "BRAZO_DERECHO_LEVANTADO"
  action:
    - service: switch.toggle
      target:
        entity_id: switch.luz_salon_forcarey
  mode: single

Probándolo todo

Llegó el momento de la prueba. Configuré el sistema, coloqué el Kinect en el salón, verifiqué las conexiones entre los sistemas y… ¡exito! La luz se encendía y se apagaba a un gesto de mi brazo. De acuerdo, un resultado trivial y divertido, pero que abre la puerta a algo bastante más interesante, y es la interacción a larga distancia de sistemas robóticos para el control de sistemas, detección de personas, etc… aplicando principios de securización mediante SD-WAN y ZTNA en infraestructuras, y con procesamiento mediante IA y ML en sistemas remotos.

Y seguimos teniendo sobre la mesa las otras mejoras que comentaba en el primer artículo de la serie. :mrgreen:

Detección de movimientos con IA como control domótico

Integración de sistemas con IA generativa: Detección de movimientos como control domótico

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.