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
colconversión instalada desde repospython3-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-desktopros-humble-ros2-controlros-humble-diff-drive-controllerros-humble-robot-localizationros-humble-slam-toolboxros-humble-nav2-bringupros-humble-nav2-coreros-humble-rviz2- Herramientas auxiliares
git≥ 2.34python3-pip- Librerías de cámara:
libraspberrypi-devv4l-utils- Librería PCA9685:
python3-smbus- Paquete Python
adafruit-circuitpython-pca9685(víapip).
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
- Instalar Ubuntu Server 22.04.4 LTS aarch64 en la microSD (usando Raspberry Pi Imager desde otro PC).
- Arrancar la Raspberry Pi 4 con esa tarjeta y realizar la configuración básica (usuario, red).
- Actualizar paquetes:
bash
sudo apt update
sudo apt upgrade -y
- 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
«`
- 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
- Inicializar
rosdep:
bash
sudo rosdep init
rosdep update
- Añadir al
~/.bashrc:
bash
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
2. Activar y probar la cámara
- Instalar utilidades:
bash
sudo apt install -y v4l-utils libraspberrypi-dev
- 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:
- Nodo Python para:
- Control del PCA9685 (servos).
- Publicación de comandos de velocidad
/cmd_velpara patrulla simple. - Nodo Python para:
- 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.xmlsetup.pyugv_beast_patrol/__init__.pypatrol_node.pycamera_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/Twistpara publicar en/cmd_vel, compatible condiff_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/Imageycv_bridgepara 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_nodeindicando fases de patrulla. - Mensajes de
camera_stream_nodeconfirmando 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):
- 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).
- 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).
- 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.
- Comprobar comandos de velocidad:
bash
ros2 topic echo /cmd_vel
Validación paso a paso
1. Validar comunicación con el PCA9685
- Desde la Raspberry Pi, antes de lanzar ROS:
bash
sudo i2cdetect -y 1
- Debe aparecer
40como 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
- Con la cámara conectada y el nodo
camera_stream_nodeen marcha:
bash
ros2 run ugv_beast_patrol camera_stream_node
- 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:
- 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 viadiff_drive_controllery hardware interface; para nivel básico, se asume que/cmd_velya se traduce a movimiento mediante tu stack). - Marca con cinta adhesiva:
- Punto inicial.
- Distancia de 3 m hacia adelante.
- ejecuta:
bash
ros2 launch ugv_beast_patrol patrol_with_camera.launch.py
- Observar:
- El robot avanza aproximadamente esos 3 m y se detiene.
- El servo gira izquierda/derecha.
- El robot retrocede y se detiene cerca de la marca inicial.
- 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 dedtparam=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ámetrodevicedecamera_stream_node. - Asegurarse de que el usuario tiene permisos sobre
/dev/videoX(en caso extremo, probar comosudosolo 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_bridgeesté 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 listque 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.xmltiene:
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_righten 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 disablecon 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:
- Integración con
diff_drive_controlleryros2_control - Crear un archivo URDF del UGV con:
base_linkleft_wheel_linkright_wheel_link
-
Configurar un hardware interface (aunque sea “dummy” inicialmente) y lanzar
diff_drive_controllerpara convertir/cmd_velen velocidades de rueda. -
Odometría e IMU con
robot_localization - Si tu UGV integra IMU, usar un YAML para
ekf_nodeque fusione/imu/datay/wheel/odom. -
Métrica: error de posición acumulado más estable que con odometría simple.
-
SLAM y navegación (
slam_toolbox,nav2) - Ejecutar
slam_toolboxen remoto para construir un mapa mientras patrulla. - Guardar mapa con
map_server. - Usar Nav2 para mandar objetivos simples, en lugar de patrulla fija.
-
Comprobar que el UGV puede repetir una ruta en el mapa guardado.
-
Control avanzado de la cámara
- Añadir un segundo servo para inclinación vertical de la cámara (usar canal 1 del PCA9685).
-
Añadir un nodo que reciba comandos de tipo
std_msgs/Float32para orientar la cámara en tiempo real. -
Grabación y análisis de rosbag
- Grabar
/camera/image_raw,/cmd_vely (cuando lo tengas)/odomconros2 bag. - 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_wscreado y compilado concolcon buildsin errores. - [ ] Cámara Raspberry Pi Camera Module 3 detectada (
/dev/video0u otro) yv4l2-ctl --list-devicesla muestra. - [ ] I2C habilitado,
i2cdetect -y 1muestra la dirección0x40del PCA9685. - [ ] Nodo
patrol_nodeejecutándose, publicando/cmd_vela 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_nodeejecutándose, publicando/camera/image_rawa la tasa configurada (≥ 10–15 fps). - [ ] Desde un PC remoto se visualiza el vídeo con
rqt_image_viewy 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
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.




