Issue reading Ping360 data via ROS 2 Python: No terminal output with serial connection

Hello everyone,

I am trying to retrieve distance data from a Ping360 sonar using ROS 2 (Humble) and Python. My goal is to detect obstacles and navigate in a pool environment.

The Pool Dimensions: 4 meters (width) x 8 meters (length).

The Problem:
When I run my Python subscriber nodes, I see no output in the terminal. I also do not see any error messages; the code runs silently. I suspect I might be using the wrong driver parameters or initializing the connection incorrectly.

Here is my setup and the codes I am using:

1. Starting the Driver (Terminal 1)
I launch the Ping360 driver with the following command via USB serial:

ros2 run ping360_sonar ping360.py --ros-args -p device:=/dev/ttyUSB0 -p baudrate:=115200 -p publish_scan:=True -p scan_threshold:=200 -p gain:=0 -p range_max:=2

2. Python Scripts (Terminal 2)
I am testing with three different scripts to read the /scan topic. None of them are producing data output with the current driver settings.

Script A: 4-Directional Radar (Monitoring 0°, 90°, 180°, 270°)

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from rclpy.qos import qos_profile_sensor_data
import math
import os

class PoolRadar(Node):
    def __init__(self):
        super().__init__('pool_radar_node')
        self.subscription = self.create_subscription(
            LaserScan, '/scan', self.scan_callback, qos_profile_sensor_data)
        
        self.distances = {
            'FRONT': float('inf'), 'REAR': float('inf'), 
            'RIGHT': float('inf'), 'LEFT': float('inf')
        }
        self.get_logger().info('--- 4-WAY RADAR STARTED ---')

    def scan_callback(self, msg):
        current_angle = msg.angle_min
        data_changed = False
        
        for r in msg.ranges:
            # Filter noise < 0.2m
            if r > 0.2 and r != float('inf'):
                deg = int(math.degrees(current_angle) % 360)
                
                # Check 4 Main Directions with tolerance
                if 170 < deg < 190:    # FRONT (180 deg)
                    self.distances['FRONT'] = r
                    data_changed = True
                elif deg > 350 or deg < 10: # REAR (0 deg)
                    self.distances['REAR'] = r
                    data_changed = True
                elif 260 < deg < 280:  # RIGHT (270 deg)
                    self.distances['RIGHT'] = r
                    data_changed = True
                elif 80 < deg < 100:   # LEFT (90 deg)
                    self.distances['LEFT'] = r
                    data_changed = True
            
            current_angle += msg.angle_increment

        if data_changed:
            self.refresh_screen()

    def refresh_screen(self):
        os.system('cls' if os.name == 'nt' else 'clear')
        front = self.distances['FRONT']
        rear = self.distances['REAR']
        right = self.distances['RIGHT']
        left = self.distances['LEFT']
        
        def fmt(val): return f"{val:.2f}m" if val != float('inf') else "---"

        print("\n" * 2)
        print(f"            FRONT (180°)")
        print(f"              {fmt(front)}")
        print(f"                |")
        print(f"LEFT (90°) {fmt(left)} --O-- {fmt(right)} RIGHT (270°)")
        print(f"                |")
        print(f"              {fmt(rear)}")
        print(f"            REAR (0°)")
        print("\n" * 2)

def main(args=None):
    rclpy.init(args=args)
    rclpy.spin(PoolRadar())
    rclpy.shutdown()

if __name__ == '__main__': main()

Script B: Front Obstacle Avoidance
(Targeting 8m length, alert at 1.5m)

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from rclpy.qos import qos_profile_sensor_data
import math

class ObstacleAvoidance(Node):
    def __init__(self):
        super().__init__('obstacle_node')
        self.subscription = self.create_subscription(
            LaserScan, '/scan', self.scan_callback, qos_profile_sensor_data)
        
        # Stop limit set to 1.5m for 8m pool length
        self.LIMIT = 1.5 
        self.get_logger().info(f'--- OBSTACLE AVOIDANCE (Limit: {self.LIMIT}m) ---')

    def scan_callback(self, msg):
        current_angle = msg.angle_min
        
        for r in msg.ranges:
            if r > 0.2 and r != float('inf'):
                deg = int(math.degrees(current_angle) % 360)
                
                # Focus only on FRONT (170-190 degrees)
                if 170 < deg < 190:
                    if r < self.LIMIT:
                        print(f"\033[91m[STOP!] OBSTACLE DETECTED! Dist: {r:.2f}m\033[0m")
                    elif r < 3.0: 
                        print(f"\033[93m[WARNING] Approaching... Dist: {r:.2f}m\033[0m")
                    else:
                        print(f"\033[92m[CLEAR] Path Clear. Dist: {r:.2f}m\033[0m")

            current_angle += msg.angle_increment

def main(args=None):
    rclpy.init(args=args)
    rclpy.spin(ObstacleAvoidance())
    rclpy.shutdown()

if __name__ == '__main__': main()

Script C: Right Wall Follower
(Targeting 4m width, keeping 1.0m distance from right wall)

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from rclpy.qos import qos_profile_sensor_data
import math

class WallFollower(Node):
    def __init__(self):
        super().__init__('wall_follower_node')
        self.subscription = self.create_subscription(
            LaserScan, '/scan', self.scan_callback, qos_profile_sensor_data)
        
        # Target: Keep 1.0m distance from the right wall
        self.TARGET = 1.0 
        print(f"--- RIGHT WALL FOLLOWER (Target: {self.TARGET}m) ---")

    def scan_callback(self, msg):
        current_angle = msg.angle_min
        
        for r in msg.ranges:
            deg = int(math.degrees(current_angle) % 360)
            
            # Focus on RIGHT sector (260-280 degrees)
            if 260 < deg < 280:
                if r > 0.2 and r != float('inf'):
                    error = r - self.TARGET
                    
                    if abs(error) < 0.10:
                        print(f"\033[92m[STRAIGHT] Perfect Position ({r:.2f}m)\033[0m")
                    elif error > 0:
                        print(f"\033[93m[TURN RIGHT >] Too far from wall ({r:.2f}m)\033[0m")
                    else:
                        print(f"\033[91m[< TURN LEFT] Too close to wall ({r:.2f}m)\033[0m")

            current_angle += msg.angle_increment

def main(args=None):
    rclpy.init(args=args)
    rclpy.spin(WallFollower())
    rclpy.shutdown()

if __name__ == '__main__': main()

My Questions:

  1. Is the scan_threshold:=200 parameter in my launch command too high? Could this be filtering out all returns and causing the lack of data?
  2. Should I adjust gain:=0 to a higher value for a pool environment?
  3. Are there any specific ROS 2 QoS settings required for the Ping360 driver that I might be missing in my ros2 run command?

Any advice on why I am receiving no data would be very helpful. Thank you!

Hi @beyza,

I haven’t used ROS for Ping360 control/scanning, but it’s always recommended to make sure the device is working (including your connection with it) before trying to do more advanced processing of its outputs.

I’d recommend visualising the scan outputs (ideally with Ping Viewer, but I believe there is also a visualisation option included in the ROS driver you’re using), so you get a sense of what data the sonar is outputting, and from there you can try to determine appropriate thresholds and configuration settings and the like.

It is always a good idea to start broad, and build up functionality one step at a time. Make an initial connection, then inspect whatever data it is giving you, then try potential filtering approaches (like handling based on different angles, and thresholding), and finally try processing the data you’re receiving, before evaluating whether it is sufficient for your use-case.

These will be much quicker for you to test than for us to try to determine for you via a theoretical discussion. Visualise the outputs and/or just try changing the values and see what happens.

Not sure, sorry. I’d suggest checking if there’s documentation for the driver you’re trying to use, or see if you can find examples in the source repository.

1 Like

Hello Eliot,

I have managed to tune the parameters (threshold and gain) within my ROS 2 setup, and I am now successfully receiving data.

As you can see in the attached screenshot, the ROS 2 driver is working correctly and detecting obstacles as shown in the image.

My question is about a non-ROS approach:

Currently, I am achieving this using the ROS 2 driver command: ros2 run ping360_sonar ping360.py --ros-args -p device:=/dev/ttyUSB0 -p baudrate:=115200 -p publish_scan:=True ...

I want to learn how to bypass ROS entirely and communicate with the Ping360 using pure Python (using the bluerobotics-ping library).

How can I establish this direct connection and retrieve the same distance/profile data using a standalone Python script, without running any ROS nodes? Could you point me to a basic example or snippet for this direct connection?

Thank you.

Glad you’ve managed to get your setup working :slight_smile:

There’s an example in the repository, for a basic connection and automatic scanning (assuming your Ping360 is using the latest firmware).

If you want to scan individual angles (rather than a single continuous range) then you’ll want to use the older / more basic control_transducer message, and receive definitions.PING360_DEVICE_DATA instead of the auto messages.

1 Like