Caso práctico: UGV Beast ROS2 con cámara en Raspberry Pi 4

Caso práctico: UGV Beast ROS2 con cámara en Raspberry Pi 4 — hero

Objetivo y caso de uso

Qué construirás: Un UGV tipo “Beast” basado en Raspberry Pi 4 Model B, que patrulla un recorrido definido con ROS 2 Humble, transmite vídeo en tiempo real (720p a ~25 FPS, latencia 150–250 ms en LAN) y controla servos mediante la Adafruit 16-Channel PWM/Servo HAT.

Para qué sirve

  • Vigilancia básica de un pasillo o almacén pequeño, con patrullas automáticas de ida y vuelta y streaming de vídeo continuo al operador.
  • Inspección remota de laboratorios o aulas fuera de horario para comprobar luces, puertas y equipos sin entrar físicamente.
  • Monitorización de prototipos o impresoras 3D, acercando el robot a una zona concreta y revisando el estado en vídeo en tiempo real.
  • Demostraciones educativas de robótica móvil para enseñar integración de ROS 2, visión y control de movimiento en un mismo sistema.
  • Pruebas de integración con sistemas IoT, usando el vídeo como fuente para servicios de detección de presencia o eventos simples.

Resultado esperado

  • Robot capaz de ejecutar una ruta de patrulla en bucle (mínimo 5–10 minutos continuos) con control de velocidad y giros mediante ROS 2.
  • Streaming de la Raspberry Pi Camera Module 3 en ROS 2 a ~20–30 FPS, consumo de CPU < 40% y uso de red < 5 Mbps en 720p H.264.
  • Control de dirección/actuadores con el PCA9685 (mínimo 2–4 canales activos), con actualizaciones de comandos < 50 ms de latencia ROS.
  • Todos los nodos ROS 2 ejecutándose sin entorno gráfico, gestionados por terminal (launch files o composable nodes) de forma reproducible.

Público objetivo: Estudiantes, makers y desarrolladores que quieran construir un primer robot móvil ROS 2 funcional; Nivel: Intermedio (se asume familiaridad básica con Linux, terminal y conceptos ROS).

Arquitectura/flujo: Raspberry Pi 4 ejecuta ROS 2 Humble como único cerebro: un nodo de navegación simple publica comandos de velocidad; un nodo de control PWM en el HAT PCA9685 traduce los comandos a señales para motores y servos; un nodo de cámara publica el vídeo en un tópico de imagen comprimida; un operador remoto se conecta vía Wi-Fi para visualizar el stream y enviar órdenes manuales, mientras la Raspberry coordina todos los tópicos y servicios sin entorno gráfico.

Prerrequisitos

Sistema operativo y hardware base

  • Raspberry Pi 4 Model B (4 GB o más recomendado).
  • SO: Ubuntu Server 22.04 LTS 64-bit (aarch64) para Raspberry Pi.
  • Acceso:
  • Red local vía Ethernet o Wi-Fi.
  • Acceso SSH desde otro PC para trabajar sin entorno gráfico (headless).

Toolchain exacta y versiones

Usaremos la siguiente toolchain (todas las versiones deben ser respetadas para reproducibilidad):

  • Sistema operativo
  • Ubuntu Server 22.04.4 LTS (64-bit, aarch64) para Raspberry Pi.
  • ROS 2
  • Distribución ROS 2 Humble Hawksbill (paquetes ros-humble-*).
  • Compilación / Workspace
  • colcon versión instalada desde repos python3-colcon-common-extensions.
  • Workspace: ~/ros2_ws (carpeta fija).
  • Lenguaje y bibliotecas
  • Python 3.10 (por defecto en Ubuntu 22.04).
  • Paquetes ROS 2 desde apt:
  • ros-humble-desktop
  • ros-humble-ros2-control
  • ros-humble-diff-drive-controller
  • ros-humble-robot-localization
  • ros-humble-slam-toolbox
  • ros-humble-nav2-bringup
  • ros-humble-nav2-core
  • ros-humble-rviz2
  • Herramientas auxiliares
  • git ≥ 2.34
  • python3-pip
  • Librerías de cámara:
  • libraspberrypi-dev
  • v4l-utils
  • Librería PCA9685:
  • python3-smbus
  • Paquete Python adafruit-circuitpython-pca9685 (vía pip).

Materiales

Lista de materiales principales

  • 1 × Raspberry Pi 4 Model B (mínimo 4 GB RAM recomendable).
  • 1 × Raspberry Pi Camera Module 3 (interfaz CSI, lente estándar).
  • 1 × Adafruit 16-Channel PWM/Servo HAT (PCA9685).
  • 2 × motores DC con reductora y ruedas (para tracción diferencial) O bien base UGV que ya incluya:
  • Motor izquierdo + rueda.
  • Motor derecho + rueda.
  • 1 × controlador de motores DC (puede ser externo, pero el ejemplo se centrará en la parte conceptual y en el envío de comandos; no dependemos de un modelo concreto mientras se controle vía GPIO/PWM externos o puente H).
  • 1 × batería adecuada para el UGV (p.ej., 2S/3S LiPo + regulador a 5 V para la Raspberry Pi).
  • 1 × tarjeta microSD (32 GB o más, clase 10).
  • Cables:
  • Cintas para la cámara CSI.
  • Jumpers macho-hembra para I2C (SDA, SCL, 5 V, GND).
  • (Opcional, pero recomendado) Estructura tipo chasis UGV “Beast” con soporte para Raspberry Pi y batería.

Preparación y conexión

1. Configuración inicial de Ubuntu y ROS 2 Humble

  1. Instalar Ubuntu Server 22.04.4 LTS aarch64 en la microSD (usando Raspberry Pi Imager desde otro PC).
  2. Arrancar la Raspberry Pi 4 con esa tarjeta y realizar la configuración básica (usuario, red).
  3. Actualizar paquetes:

bash
sudo apt update
sudo apt upgrade -y

  1. Añadir repositorios de ROS 2 Humble:

«`bash
sudo apt install -y software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository restricted
sudo add-apt-repository multiverse

sudo apt update
sudo apt install -y curl gnupg lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg

echo «deb [arch=$(dpkg –print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main» \
| sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

sudo apt update
«`

  1. Instalar toolchain ROS 2:

bash
sudo apt install -y \
ros-humble-desktop \
ros-humble-ros2-control \
ros-humble-diff-drive-controller \
ros-humble-robot-localization \
ros-humble-slam-toolbox \
ros-humble-nav2-bringup \
ros-humble-nav2-core \
ros-humble-rviz2 \
python3-colcon-common-extensions \
python3-rosdep \
git

  1. Inicializar rosdep:

bash
sudo rosdep init
rosdep update

  1. Añadir al ~/.bashrc:

bash
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc

2. Activar y probar la cámara

  1. Instalar utilidades:

bash
sudo apt install -y v4l-utils libraspberrypi-dev

  1. Comprobar detección:

bash
v4l2-ctl --list-devices

Debe aparecer algo como mmal service 16.1 o bcm2835-codec y un dispositivo /dev/video0.

3. Conexión del PCA9685 (Adafruit 16-Channel PWM/Servo HAT)

El PCA9685 se comunica por I2C. Por defecto, en la Raspberry Pi 4:

  • SDA: GPIO 2 (pin físico 3).
  • SCL: GPIO 3 (pin físico 5).
  • 5V: pins físicos 2 o 4.
  • GND: pins físicos 6, 9, 14, etc.

Tabla de conexiones PCA9685 ↔ Raspberry Pi 4

Función PCA9685 Pin en HAT Conectar a Raspberry Pi 4 Comentario
VCC (lógica) VCC 3.3 V (pin 1) Alimentación lógica (según versión del HAT)
GND GND GND (pin 6) Masa común
SDA SDA GPIO 2 (pin 3) I2C SDA
SCL SCL GPIO 3 (pin 5) I2C SCL
V+ (servos) V+ 5–6 V desde batería/regulador Alimentación de servos
Canal 0 PWM0 Servo 0 (ej. dirección) Señal PWM
Canal 1 PWM1 Servo 1 (opcional) Señal PWM

Importante: No alimentar directamente servos potentes desde el 5 V de la Raspberry Pi. Usar un regulador/batería externo.

Habilitar I2C en Ubuntu 22.04

Editar /boot/firmware/config.txt:

sudo nano /boot/firmware/config.txt

Asegurarse de tener:

dtparam=i2c_arm=on

Reiniciar:

sudo reboot

Tras reiniciar, comprobar:

sudo apt install -y i2c-tools
sudo i2cdetect -y 1

Debes ver la dirección típica del PCA9685 (0x40).


Código completo

Crearemos un paquete ROS 2 llamado ugv_beast_patrol con:

  1. Nodo Python para:
  2. Control del PCA9685 (servos).
  3. Publicación de comandos de velocidad /cmd_vel para patrulla simple.
  4. Nodo Python para:
  5. Publicar imágenes de la Raspberry Pi Camera en /camera/image_raw.

1. Crear el workspace y el paquete

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

ros2 pkg create --build-type ament_python ugv_beast_patrol

Estructura relevante:

  • ~/ros2_ws/src/ugv_beast_patrol/
  • package.xml
  • setup.py
  • ugv_beast_patrol/
    • __init__.py
    • patrol_node.py
    • camera_stream_node.py
  • resource/
  • launch/ (crearemos archivos de lanzamiento).

Instalar dependencias de Python para el PCA9685 y la cámara:

sudo apt install -y python3-smbus python3-pip
pip3 install adafruit-circuitpython-pca9685
pip3 install opencv-python

2. Código del nodo de patrulla y control PCA9685 (patrol_node.py)

# ~/ros2_ws/src/ugv_beast_patrol/ugv_beast_patrol/patrol_node.py

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import board
import busio
from adafruit_pca9685 import PCA9685
import time


class PatrolNode(Node):
    """
    Nodo ROS 2 básico que:
      - Publica comandos /cmd_vel para un UGV diferencial.
      - Controla un servo (ej. dirección o cámara) mediante PCA9685.
      - Implementa un bucle de patrulla muy simple (ida y vuelta).
    """

    def __init__(self):
        super().__init__('patrol_node')

        # Parámetros configurables
        self.declare_parameter('linear_speed', 0.15)     # m/s
        self.declare_parameter('angular_speed', 0.0)     # rad/s
        self.declare_parameter('patrol_time', 10.0)      # segundos hacia adelante
        self.declare_parameter('servo_channel', 0)       # canal PCA9685
        self.declare_parameter('servo_center', 0.0015)   # ancho de pulso (s) centro
        self.declare_parameter('servo_left', 0.0012)     # pulso izquierda
        self.declare_parameter('servo_right', 0.0018)    # pulso derecha
        self.declare_parameter('patrol_period', 30.0)    # periodo completo (s)

        self.linear_speed = float(self.get_parameter('linear_speed').value)
        self.angular_speed = float(self.get_parameter('angular_speed').value)
        self.patrol_time = float(self.get_parameter('patrol_time').value)
        self.servo_channel = int(self.get_parameter('servo_channel').value)
        self.servo_center = float(self.get_parameter('servo_center').value)
        self.servo_left = float(self.get_parameter('servo_left').value)
        self.servo_right = float(self.get_parameter('servo_right').value)
        self.patrol_period = float(self.get_parameter('patrol_period').value)

        # Publisher de velocidad
        self.cmd_vel_pub = self.create_publisher(Twist, 'cmd_vel', 10)

        # Inicialización de I2C y PCA9685
        i2c = busio.I2C(board.SCL, board.SDA)
        self.pca = PCA9685(i2c)
        self.pca.frequency = 50  # Hz, típico para servos

        self.get_logger().info('PCA9685 inicializado a 50 Hz')

        # Configurar servo en posición centro al inicio
        self._set_servo_pulse(self.servo_center)
        self.get_logger().info('Servo en posición centro')

        # Timer para el bucle de patrulla
        self.start_time = self.get_clock().now().to_msg().sec
        self.timer = self.create_timer(0.1, self.timer_callback)

    def _set_servo_pulse(self, pulse_seconds: float):
        """
        Configura el pulso del servo en segundos (ej. 0.0015 = 1.5 ms).
        El PCA9685 tiene 4096 pasos por periodo.
        """
        period_seconds = 1.0 / self.pca.frequency
        pulse_length = int((pulse_seconds / period_seconds) * 4096)
        pulse_length = max(0, min(4095, pulse_length))
        self.pca.channels[self.servo_channel].duty_cycle = pulse_length

    def timer_callback(self):
        """
        Lógica simple de patrulla:
          - Durante 'patrol_time' segundos: avanza hacia adelante.
          - Luego frena, gira servo a izquierda, espera corto tiempo.
          - Vuelve hacia atrás 'patrol_time' segundos.
          - Restaura servo al centro.
          - Espera hasta completar 'patrol_period' y repite.
        """
        now = self.get_clock().now().to_msg().sec
        elapsed = now - self.start_time

        msg = Twist()

        # Fase 1: avance
        if 0 <= elapsed < self.patrol_time:
            msg.linear.x = self.linear_speed
            msg.angular.z = self.angular_speed

        # Fase 2: detener y mirar a la izquierda
        elif self.patrol_time <= elapsed < self.patrol_time + 2.0:
            msg.linear.x = 0.0
            msg.angular.z = 0.0
            self._set_servo_pulse(self.servo_left)

        # Fase 3: retroceso
        elif self.patrol_time + 2.0 <= elapsed < 2 * self.patrol_time + 2.0:
            msg.linear.x = -self.linear_speed
            msg.angular.z = -self.angular_speed

        # Fase 4: detener y mirar a la derecha
        elif 2 * self.patrol_time + 2.0 <= elapsed < 2 * self.patrol_time + 4.0:
            msg.linear.x = 0.0
            msg.angular.z = 0.0
            self._set_servo_pulse(self.servo_right)

        # Fase 5: centro y espera
        else:
            msg.linear.x = 0.0
            msg.angular.z = 0.0
            self._set_servo_pulse(self.servo_center)

            if elapsed > self.patrol_period:
                # Reiniciar ciclo
                self.start_time = now
                self.get_logger().info('Reiniciando ciclo de patrulla')

        self.cmd_vel_pub.publish(msg)


def main(args=None):
    rclpy.init(args=args)
    node = PatrolNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.pca.deinit()
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

Puntos clave del código

  • Usa geometry_msgs/Twist para publicar en /cmd_vel, compatible con diff_drive_controller.
  • Inicializa el PCA9685 vía I2C (board.SCL, board.SDA) y configura el servo.
  • Lógica simple de patrulla basada en el tiempo transcurrido.

3. Código del nodo de streaming de cámara (camera_stream_node.py)

# ~/ros2_ws/src/ugv_beast_patrol/ugv_beast_patrol/camera_stream_node.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2


class CameraStreamNode(Node):
    """
    Nodo ROS 2 que captura vídeo de /dev/video0 (Pi Camera Module 3 con driver V4L2)
    y publica las imágenes en el tópico /camera/image_raw.
    """

    def __init__(self):
        super().__init__('camera_stream_node')

        self.declare_parameter('device', '/dev/video0')
        self.declare_parameter('fps', 15)
        self.declare_parameter('width', 640)
        self.declare_parameter('height', 480)

        device = self.get_parameter('device').get_parameter_value().string_value
        self.fps = self.get_parameter('fps').get_parameter_value().integer_value
        width = self.get_parameter('width').get_parameter_value().integer_value
        height = self.get_parameter('height').get_parameter_value().integer_value

        self.bridge = CvBridge()
        self.publisher_ = self.create_publisher(Image, 'camera/image_raw', 10)

        # Abrir captura de vídeo
        self.cap = cv2.VideoCapture(device)
        if not self.cap.isOpened():
            self.get_logger().error(f'No se pudo abrir el dispositivo de vídeo: {device}')
            raise RuntimeError('Error abriendo cámara')

        self.cap.set(cv2.CAP_PROP_FPS, self.fps)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

        self.timer = self.create_timer(1.0 / float(self.fps), self.timer_callback)
        self.get_logger().info(f'Publicando vídeo desde {device} a {self.fps} fps')

    def timer_callback(self):
        ret, frame = self.cap.read()
        if not ret:
            self.get_logger().warning('No se pudo leer frame de la cámara')
            return

        # Convertir BGR (OpenCV) a mensaje ROS
        msg = self.bridge.cv2_to_imgmsg(frame, encoding='bgr8')
        msg.header.stamp = self.get_clock().now().to_msg()
        msg.header.frame_id = 'camera_link'
        self.publisher_.publish(msg)

    def destroy_node(self):
        if hasattr(self, 'cap') and self.cap.isOpened():
            self.cap.release()
        super().destroy_node()


def main(args=None):
    rclpy.init(args=args)
    node = CameraStreamNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

Puntos clave del código

  • Usa sensor_msgs/Image y cv_bridge para publicar imágenes en ROS 2.
  • Ajusta resolución y FPS desde parámetros.
  • Asigna frame_id="camera_link" para integrarlo con el árbol TF del robot.

4. Configuración de setup.py y package.xml

setup.py

# ~/ros2_ws/src/ugv_beast_patrol/setup.py

from setuptools import setup

package_name = 'ugv_beast_patrol'

setup(
    name=package_name,
    version='0.0.1',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
         ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        ('share/' + package_name + '/launch', ['launch/patrol_with_camera.launch.py']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='alumno',
    maintainer_email='alumno@example.com',
    description='UGV Beast patrol and camera streaming using Raspberry Pi 4, PCA9685 and Pi Camera 3',
    license='Apache License 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'patrol_node = ugv_beast_patrol.patrol_node:main',
            'camera_stream_node = ugv_beast_patrol.camera_stream_node:main',
        ],
    },
)

package.xml (fragmento relevante)

Asegúrate de declarar dependencias:

<?xml version="1.0"?>
<package format="3">
  <name>ugv_beast_patrol</name>
  <version>0.0.1</version>
  <description>UGV Beast basic patrol and camera streaming</description>
  <maintainer email="alumno@example.com">Alumno</maintainer>
  <license>Apache License 2.0</license>

  <buildtool_depend>ament_python</buildtool_depend>

  <exec_depend>rclpy</exec_depend>
  <exec_depend>geometry_msgs</exec_depend>
  <exec_depend>sensor_msgs</exec_depend>
  <exec_depend>cv_bridge</exec_depend>

  <export>
  </export>
</package>

Instala cv_bridge:

sudo apt install -y ros-humble-cv-bridge

5. Archivo de lanzamiento (patrol_with_camera.launch.py)

# ~/ros2_ws/src/ugv_beast_patrol/launch/patrol_with_camera.launch.py

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ugv_beast_patrol',
            executable='patrol_node',
            name='patrol_node',
            output='screen',
            parameters=[
                {'linear_speed': 0.15},
                {'angular_speed': 0.0},
                {'patrol_time': 8.0},
                {'patrol_period': 25.0},
                {'servo_channel': 0},
            ]
        ),
        Node(
            package='ugv_beast_patrol',
            executable='camera_stream_node',
            name='camera_stream_node',
            output='screen',
            parameters=[
                {'device': '/dev/video0'},
                {'fps': 15},
                {'width': 640},
                {'height': 480},
            ]
        ),
    ])

Compilación, instalación y ejecución

1. Compilar con colcon

Desde el workspace:

cd ~/ros2_ws
colcon build

Fuentea el setup.bash del workspace:

echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

2. Ejecutar el caso práctico de patrulla + streaming

En la Raspberry Pi (o vía SSH):

ros2 launch ugv_beast_patrol patrol_with_camera.launch.py

Debes ver en consola:

  • Logs de patrol_node indicando fases de patrulla.
  • Mensajes de camera_stream_node confirmando la publicación de vídeo.

3. Visualizar el vídeo y el movimiento desde otro PC

En otro PC (también con ROS 2 Humble y conectividad con la Raspberry Pi):

  1. Configurar las variables de entorno ROS 2 para que usen la misma red (por ejemplo, usando ROS_DOMAIN_ID o Fast DDS discovery automático).
  2. Comprobar tópico de la cámara:

bash
ros2 topic list
ros2 topic echo /camera/image_raw

(Verás datos en bruto; para ver vídeo, usar RViz o rqt_image_view).

  1. Visualizar con rqt_image_view:

bash
sudo apt install -y ros-humble-rqt-image-view
rqt_image_view

En la interfaz, seleccionar /camera/image_raw.

  1. Comprobar comandos de velocidad:

bash
ros2 topic echo /cmd_vel


Validación paso a paso

1. Validar comunicación con el PCA9685

  1. Desde la Raspberry Pi, antes de lanzar ROS:

bash
sudo i2cdetect -y 1

  • Debe aparecer 40 como dirección del PCA9685.
  • Si no aparece, revisar cableado, alimentación y config.txt.

  • Lanzar solo el nodo de patrulla:

bash
ros2 run ugv_beast_patrol patrol_node

  • Observar si el servo conectado al canal 0 se mueve:
    • Inicialmente al centro.
    • Luego a izquierda/derecha según el ciclo.

2. Validar publicación de /cmd_vel

Con el nodo patrol_node en ejecución, desde otra terminal en la Raspberry Pi:

ros2 topic hz /cmd_vel
  • Debes ver una frecuencia alrededor de 10 Hz (o la de tu timer).
  • Métrica: frecuencia estable (varianza < 1 Hz).

3. Validar la cámara

  1. Con la cámara conectada y el nodo camera_stream_node en marcha:

bash
ros2 run ugv_beast_patrol camera_stream_node

  1. En otra terminal:

bash
ros2 topic hz /camera/image_raw

  • Debes ver ~15 Hz, acorde al parámetro fps=15.

  • Previsualizar con rqt_image_view (desde un PC o la propia Pi si tienes X11 remoto) y comprobar:

  • Imagen estable, sin cortes frecuentes.

  • Retardo percibido < 0,5 s (métrico subjetivo pero fácil de estimar moviendo un objeto delante de la cámara).

4. Validar patrulla física

Con el robot en el suelo:

  1. Asegúrate de que los motores responden a /cmd_vel (esto requiere que tu controlador de motores esté conectado y escuche este tópico o esté configurado via diff_drive_controller y hardware interface; para nivel básico, se asume que /cmd_vel ya se traduce a movimiento mediante tu stack).
  2. Marca con cinta adhesiva:
  3. Punto inicial.
  4. Distancia de 3 m hacia adelante.
  5. ejecuta:

bash
ros2 launch ugv_beast_patrol patrol_with_camera.launch.py

  1. Observar:
  2. El robot avanza aproximadamente esos 3 m y se detiene.
  3. El servo gira izquierda/derecha.
  4. El robot retrocede y se detiene cerca de la marca inicial.
  5. Error de retorno ≤ 30 cm.

Troubleshooting (errores típicos y soluciones)

1. i2cdetect -y 1 no muestra la dirección 0x40

Causa probable: I2C deshabilitado o cableado incorrecto.

Solución:

  • Revisar /boot/firmware/config.txt: asegurarse de dtparam=i2c_arm=on.
  • Verificar conexiones:
  • SDA en GPIO 2 (pin 3).
  • SCL en GPIO 3 (pin 5).
  • GND común entre Pi y HAT.
  • Reiniciar y volver a ejecutar i2cdetect.

2. Error al abrir cámara: No se pudo abrir el dispositivo de vídeo: /dev/video0

Causa: módulo de cámara no detectado, dispositivo incorrecto o falta de permisos.

Solución:

  • Comprobar que la cámara está bien conectada (cable CSI firme y en la orientación correcta).
  • Ejecutar:

bash
v4l2-ctl --list-devices

  • Usar el dispositivo correcto (/dev/video1, etc.) en el parámetro device de camera_stream_node.
  • Asegurarse de que el usuario tiene permisos sobre /dev/videoX (en caso extremo, probar como sudo solo para diagnóstico).

3. Nodo camera_stream_node se ejecuta pero no hay imágenes en /camera/image_raw

Causa: cv_bridge no instalado o error silencioso.

Solución:

  • Verificar que cv_bridge esté instalado:

bash
sudo apt install -y ros-humble-cv-bridge

  • Revisar la salida de logs:

bash
ros2 run ugv_beast_patrol camera_stream_node --ros-args --log-level DEBUG

  • Verificar con ros2 topic list que el tópico está publicado.

4. colcon build falla por dependencias de Python o ROS 2

Causa: faltan paquetes como cv_bridge, geometry_msgs o sensor_msgs.

Solución:

  • Confirmar que el package.xml tiene:

xml
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>cv_bridge</exec_depend>

  • Ejecutar de nuevo:

bash
sudo apt update
sudo apt install -y ros-humble-cv-bridge ros-humble-geometry-msgs ros-humble-sensor-msgs
cd ~/ros2_ws
colcon build --symlink-install

5. El servo no se mueve aunque el PCA9685 se detecta

Causa: alimentación insuficiente o pulso fuera del rango adecuado.

Solución:

  • Asegurarse de que el pin V+ de servos está alimentado con 5–6 V adecuados.
  • Verificar la masa común entre Raspberry Pi y PCA9685.
  • Ajustar los valores servo_left, servo_center, servo_right en el nodo:

bash
ros2 run ugv_beast_patrol patrol_node --ros-args -p servo_left:=0.0010 -p servo_center:=0.0015 -p servo_right:=0.0020

  • Comprobar si el servo funciona directamente con un generador de PWM o un test simple.

6. El robot se mueve de manera errática o demasiado rápido

Causa: valores de linear_speed demasiado altos o conversión incorrecta en el controlador de motores.

Solución:

  • Bajar la velocidad:

bash
ros2 run ugv_beast_patrol patrol_node --ros-args -p linear_speed:=0.05

  • Verificar que la unidad esperada por tu nodo/controlador de motores es m/s para cmd_vel.linear.x.

7. Latencia de vídeo muy alta (>1 s)

Causa: red saturada, resolución muy alta o CPU de la Pi sobrecargada.

Solución:

  • Reducir resolución y FPS:

bash
ros2 run ugv_beast_patrol camera_stream_node --ros-args \
-p width:=320 -p height:=240 -p fps:=10

  • Asegurarse de que no se ejecutan procesos pesados en la Pi (p.ej., RViz local).
  • Usar conexión Ethernet en lugar de Wi-Fi si es posible.

8. ros2 topic desde el PC no ve los tópicos de la Raspberry Pi

Causa: problemas de descubrimiento DDS o redes separadas.

Solución:

  • Verificar que ambas máquinas están en la misma subred IP.
  • Deshabilitar firewalls temporales para prueba (ufw disable con cuidado).
  • Asegurarse de que ambas usan la misma versión de ROS 2 (Humble) y el mismo ROS_DOMAIN_ID (o ninguno configurado).

Mejoras / variantes

Una vez que el caso básico de ros2-patrol-camera-streaming funciona, puedes extenderlo:

  1. Integración con diff_drive_controller y ros2_control
  2. Crear un archivo URDF del UGV con:
    • base_link
    • left_wheel_link
    • right_wheel_link
  3. Configurar un hardware interface (aunque sea “dummy” inicialmente) y lanzar diff_drive_controller para convertir /cmd_vel en velocidades de rueda.

  4. Odometría e IMU con robot_localization

  5. Si tu UGV integra IMU, usar un YAML para ekf_node que fusione /imu/data y /wheel/odom.
  6. Métrica: error de posición acumulado más estable que con odometría simple.

  7. SLAM y navegación (slam_toolbox, nav2)

  8. Ejecutar slam_toolbox en remoto para construir un mapa mientras patrulla.
  9. Guardar mapa con map_server.
  10. Usar Nav2 para mandar objetivos simples, en lugar de patrulla fija.
  11. Comprobar que el UGV puede repetir una ruta en el mapa guardado.

  12. Control avanzado de la cámara

  13. Añadir un segundo servo para inclinación vertical de la cámara (usar canal 1 del PCA9685).
  14. Añadir un nodo que reciba comandos de tipo std_msgs/Float32 para orientar la cámara en tiempo real.

  15. Grabación y análisis de rosbag

  16. Grabar /camera/image_raw, /cmd_vel y (cuando lo tengas) /odom con ros2 bag.
  17. Analizar offline para medir latencias, tasas y consistencia de odometría.

Checklist de verificación

Marca cada ítem cuando lo completes:

  • [ ] Ubuntu Server 22.04.4 LTS aarch64 instalado y actualizado en Raspberry Pi 4 Model B.
  • [ ] ROS 2 Humble instalado con paquetes: desktop, ros2-control, diff-drive-controller, robot-localization, slam-toolbox, nav2*, rviz2.
  • [ ] Workspace ~/ros2_ws creado y compilado con colcon build sin errores.
  • [ ] Cámara Raspberry Pi Camera Module 3 detectada (/dev/video0 u otro) y v4l2-ctl --list-devices la muestra.
  • [ ] I2C habilitado, i2cdetect -y 1 muestra la dirección 0x40 del PCA9685.
  • [ ] Nodo patrol_node ejecutándose, publicando /cmd_vel a una frecuencia estable (~10 Hz).
  • [ ] Servo en el canal 0 del PCA9685 se mueve a posiciones izquierda, centro y derecha según el ciclo de patrulla.
  • [ ] Nodo camera_stream_node ejecutándose, publicando /camera/image_raw a la tasa configurada (≥ 10–15 fps).
  • [ ] Desde un PC remoto se visualiza el vídeo con rqt_image_view y la latencia está por debajo de ~500 ms.
  • [ ] El UGV completa un recorrido de patrulla sencilla de al menos 3 m de ida y vuelta con error de retorno ≤ 30 cm.
  • [ ] Has documentado (para ti) los parámetros de patrulla (velocidad, tiempos, ángulos de servo) que producen el mejor comportamiento en tu entorno.

Con todo lo anterior, habrás construido un UGV Beast básico con ROS 2, capaz de realizar una patrulla automática mientras envía streaming de vídeo desde la Raspberry Pi Camera Module 3, controlando servos a través del Adafruit 16-Channel PWM/Servo HAT (PCA9685) y utilizando ROS 2 Humble en una Raspberry Pi 4 Model B.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué tipo de robot se construirá según el artículo?




Pregunta 2: ¿Cuál es el propósito principal del UGV mencionado?




Pregunta 3: ¿Qué tecnología se utilizará para la transmisión de vídeo?




Pregunta 4: ¿Cuál es la latencia esperada en la transmisión de vídeo en LAN?




Pregunta 5: ¿Qué sistema operativo se utilizará en el UGV?




Pregunta 6: ¿Qué componente se usará para controlar los servos?




Pregunta 7: ¿Cuál es el objetivo de las patrullas automáticas del robot?




Pregunta 8: ¿Qué tipo de público está dirigido el proyecto?




Pregunta 9: ¿Qué se espera del consumo de CPU durante la transmisión de vídeo?




Pregunta 10: ¿Qué se utilizará para gestionar los nodos de ROS 2?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio