Construyendo un Node Subscriber Básico para LaserScan
El Código¶
Copia todo el código a continuación en tu archivo lidar_subscriber.py y luego revisa las anotaciones para entender cómo funciona todo.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.signals import SignalHandlerOptions # (1)!
from sensor_msgs.msg import LaserScan # (2)!
import numpy as np # (3)!
class LidarSubscriber(Node):
def __init__(self):
super().__init__("lidar_subscriber")
self.lidar_sub = self.create_subscription(
msg_type=LaserScan,
topic="/scan",
callback=self.lidar_callback,
qos_profile=10,
) # (4)!
self.get_logger().info(f"The '{self.get_name()}' node is initialised.")
def lidar_callback(self, scan_data: LaserScan):
left = scan_data.ranges[0:21]
right = scan_data.ranges[-20:] # (5)!
front = np.array(right + left) # (6)!
valid_data = front[front != float("inf")] # (7)!
if np.shape(valid_data)[0] > 0: # (8)!
single_point_average = valid_data.mean() # (9)!
else:
single_point_average = float("nan") # (10)!
self.get_logger().info(
f"LiDAR Reading (front): {single_point_average:.3f} meters.",
throttle_duration_sec = 1,
) # (11)!
def main(args=None):
rclpy.init(
args=args,
signal_handler_options=SignalHandlerOptions.NO
)
node = LidarSubscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
print("Shutdown request (Ctrl+C) detected...")
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
- Nada de esto debería ser nuevo para ti en este punto. Recuerda de la Parte 2 que estamos usando
SignalHandlerOptionspara manejar solicitudes de apagado (activadas con Ctrl+C). - Aquí estamos construyendo un subscriber para el topic
/scan, y sabemos que este topic usa el tipo de interfazsensor_msgs/msg/LaserScan, así que lo importamos aquí. numpyes una librería Python que nos permite trabajar con datos numéricos, muy útil para arreglos grandes comoranges.- Construimos un subscriber de la misma manera que lo hemos hecho en las Partes 1 y 2, pero esta vez apuntando al topic
/scan. -
Desde el frente del robot, obtenemos un arco de 20° de datos de escaneo a cada lado del eje x (consulta la figura a continuación).
-
Luego combinamos los arreglos de datos
left_20_degyright_20_deg, y los convertimos de una lista Python a un arreglonumpy(consulta la figura a continuación). -
Esto ilustra una de las grandes características de los arreglos
numpy: podemos filtrarlos.Recuerda que
frontahora es un arreglonumpyque contiene 40 puntos de datos.Recuerda también que típicamente habrá varios valores
infdispersos en el arreglo LaserScan, resultado de lecturas del sensor que están fuera del rango de medición del sensor (es decir, mayores querange_maxo menores querange_min). Necesitamos eliminar estos, así que le pedimos anumpyque filtre nuestro arreglo de la siguiente manera:-
De todos los valores en el arreglo
front, determina cuáles no son iguales ainf:front != float("inf") -
Usa este filtro para eliminar los valores
infdel arreglofront:front[front != float("inf")] -
Devuelve esto como un nuevo arreglo
numpyllamadovalid_data:valid_data = front[front != float("inf")]
-
-
En ciertas situaciones (por ejemplo, en entornos muy vacíos) todos los valores podrían ser iguales a
inf(imagina la simulación "empty world"). Aquí verificamos el tamaño del arreglovalid_datapara asegurarnos de no haber eliminado todos los valores con el filtrado anterior. -
Si el arreglo no está vacío, usa el método
mean()para determinar el promedio de todas las lecturas dentro del conjunto de datos. - Si el arreglo está vacío, devuelve "not a number" (es decir, "nan") en su lugar.
-
Imprime este valor en el terminal, pero limita los mensajes para que solo se muestre uno por segundo.
Question
Si no limitáramos esto, ¿a qué tasa se imprimirían los mensajes?
El procesamiento de datos se ilustra en la figura a continuación:
Dependencias del Package¶
Este node tiene dependencias en dos librerías Python externas (además de rclpy):
Por lo tanto, debes incluirlas en el archivo package.xml (bajo la línea <exec_depend>rclpy</exec_depend>):