4. Lidar Lesson

4.1 Introduction to Lidar

4.1.1 Product Description

Lidar is a remote sensing technology that uses laser beams to detect target characteristics such as position and velocity. It offers advantages including high measurement resolution, strong penetration ability, robust anti-interference capability, and effective stealth detection.

According to the measurement principle, Lidar can be classified into three types: triangulation Lidar, pulse Lidar, and coherent Lidar. The Lidar equipped on the MentorPi is of the pulse lidar type.

4.1.2 Lidar Working Principle

A Lidar system consists of a laser emission system, a scanning system, a laser receiving system, and a signal processing system.

First, the laser emission system sends out a detection signal (laser beam) toward the target. The scanning system scans the plane to gather planar information.

Next, the laser receiving system captures the laser beam reflected from the target object and generates a received signal.

Finally, the signal processing system processes the received signal to extract features such as the target’s surface shape and physical properties (e.g., position, height, and velocity), thereby enabling the construction of a 3D model of the object.

4.1.3 Lidar Parameter

  • Communication Interface

The MS200 lidar connects to external systems via a connector for power supply and data transmission. For detailed interface definitions and parameter requirements, refer to the diagram/table below.

Pin Signal Attribute Description
1 Tx Serial data transmission Tx (Transmitting from this device, 0V~3.3V)
2 Rx Processing Incoming Data Rx
3 GND Power supply input (positive) GND
4 VCC Power supply input (positive) DC 5V(4.5V~5.5V)
  • Electrical Parameter

Before use, it is essential to understand the operating conditions of the lidar. Please ensure that the external power supply is properly designed or selected according to the Oradar MS200’s input voltage range and peak startup current, to guarantee stable and reliable operation of the device.

Parameter Minimum value Typical value Maximum value Remarks
Operating power 4.5V 5.0V 5.5V Pc power supply
Voltage ripple - - 100mV
Startup current - - 400mA
Operating current 180mA 230mA 280mA
Operating temperature -10℃ 25℃ 50℃
Storage temperature 20℃ 25℃ 70℃
Operating temperature 0 30% 80% Non-condensing
  • Specification Parameter

Model MS200:
Type Short-range
Distance Measurement Principle TOF (Time of Flight) ranging, suitable for both indoor and outdoor applications
Scanning Angle 360°
Angular Resolution 0.8°
Measurement Frequency 4500 times per second
Scanning Frequency 7-15Hz
Dimensions 37.7*37.5*33mm
Weight 40g
Distance Measurement Accuracy: 3 m to 12 m: ±20 mm
Light Source 905 nm laser
Measurement Range White objects: 13.4 m
Black objects: 13.4 m
Minimum Measurement Distance 0.1m
Ambient Light Resistance 30 KLux (illumination intensity)
Power Supply DC5V±0.5V
Operating Temperature Working: -10°C to 50°C
Storage: -30°C to 70°C
Drive Method Built-in brushless motor
Windows Support Provides Windows-based PC software
ROS Support ROS1/ROS2
Communication port Standard asynchronous serial port (UART)

4.2 Lidar Working and Ranging Principle

4.2.1 Lidar Ranging

Two common methods are employed by Lidar to determine the distance to a target: triangulation and Time of Flight (TOF).

In the case of TOF, as depicted in the diagram, the Lidar initially projects light onto the object. The object reflects the light directly back to the Lidar, which calculates the time it takes for the light to return. The distance between the object and the Lidar is then obtained by multiplying this time by the speed of light.

In triangulation, as illustrated in the diagram, the Lidar undergoes adjustments during manufacturing to ensure that the light does not directly strike the object. Instead, it is projected at a specific angle, a pre-set value that remains constant during operation. The distance from the object to the Lidar can be calculated by incorporating this angle into trigonometric functions.

4.2.2 Lidar Working Result

Its working result is as follows. Lidar will emit light and shine it on the object surface. When receiving the light reflected by the object, Lidar will mark the contour of the object at the position where the light is reflected.

4.3 Lidar Obstacle Avoidance

For instructions on app connection, please consult the tutorial located in “1. Getting Ready->1.6 APP Control”.

4.3.1 Enable Game

  • Initiate Lidar Game through App

(1) Connect the robot to “WonderPi” app.

(2) Click-on Lidar to navigate to the game interface.

(3) Switch on “Avoid obstacle” button to start the game.

  • Initiate Lidar Game Using Command

(1) Start the robot, and access the robot system desktop using VNC according to the tutorial saved in “2. Set Development Environment->2.1 VNC Installation and Connection”.

(2) Click-on to open the command line terminal.

(3) Execute the command and press Enter to deactivate the automatic start service for the app.

~/.stop_ros.sh

(4) Run the command to enable the local services for app-related game and chassis control services.

ros2 launch app lidar_node.launch.py debug:=true

(5) Open a new terminal, and execute the command. Then, press Enter to start the Lidar game.

ros2 service call /lidar_app/enter std_srvs/srv/Trigger {}

Note

The robot’s performance initiated by the app and command is identical.

(6) Enter the command and hit Enter to launch Lidar obstacle avoidance game.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 1}"

(7) If you want to exit the game, enter the command and press “Enter”.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 0}"

Note

The game will continue to run under the current Raspberry Pi power-on state if not exited. To avoid excessive use of the Raspberry Pi’s operating memory, please follow the above instructions to close the current game before executing other games.

(8) If you need to terminate the program, press short-cut “Ctrl+C” on the terminal opened in steps (4) and (5).

After experiencing the Lidar game, you can activate the app service either by using a command or restarting the robot. If the app service is not activated, related app functions will be disabled. In the case of a robot restart, the app service will start automatically.

Click and enter the command. Press enter to start the app, and wait for the buzzer to beep. Please enter the command in the system path, not in the Docker container.

sudo systemctl restart start_node.service

4.3.2 Program Outcome

Let’s use a book as the object to be detected. In the function of Lidar following, the object to be detected needs to be higher than the scanning height of the Lidar. Only in this way the Lidar mounted on the MentorPi can effectively scan its position information. Then, the MentorPi moves forward. When the obstacle is detected, the MentorPi will automatically turn to avoid it.

4.3.3 Program Analysis

  • Launch File

The launch file is located in: /home/ubuntu/ros2_ws/src/app/launch/lidar_node.launch.py

(1) Set the storage path

Retrieve the paths for the two packages: peripherals and controller.

14
15
16
17
18
19
    if compiled == 'True':
        controller_package_path = get_package_share_directory('controller')
        peripherals_package_path = get_package_share_directory('peripherals')
    else:
        controller_package_path = '/home/ubuntu/ros2_ws/src/driver/controller'
        peripherals_package_path = '/home/ubuntu/ros2_ws/src/peripherals'

(2) Initiate other Launch files

21
22
23
24
25
26
27
28
29
30
31
32
    lidar_controller_node = GroupAction([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(peripherals_package_path, 'launch/lidar.launch.py')),
            condition=IfCondition(debug),
            ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(controller_package_path, 'launch/controller.launch.py')),
            condition=IfCondition(debug),
            ),

lidar.launch.py Lidar launch

controller.launch.py Motion control launch

(3) Initiate Node

34
35
36
37
38
39
        Node(
            package='app',
            executable='lidar_controller',
            output='screen',
            ),
    ])

Enable Lidar game node.

  • Source Code File

The source code of the program is saved in: /home/ubuntu/ros2_ws/src/app/app/lidar_controller.py

The process logic of the program based on the implemented effect is shown in the diagram:

(1) Initialization

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class LidarController(Node):
    def __init__(self, name):
        rclpy.init()
        super().__init__(name)

        self.name = name
        self.running_mode = 0
        self.threshold = 0.6  # meters
        self.scan_angle = math.radians(360)  # radians
        self.speed = 0.2
        self.last_act = 0
        self.timestamp = 0
        self.angle_data = []
        # pid参数
        self.pid_yaw = pid.PID(1.6, 0, 0.16)
        self.pid_dist = pid.PID(1.7, 0, 0.16)
        self.lock = threading.RLock()
        self.lidar_sub = None
        self.lidar_type = os.environ.get('LIDAR_TYPE')
        self.machine_type = os.environ.get('MACHINE_TYPE')

self.name: stores the incoming name as the property of the object.

Other attributes include parameters for controlling the Lidar, such as operating mode, threshold, scanning angle, and speed.

self.lock: creates a thread lock for safe access in a multi-threaded environment.

(2) Create ROS service

46
47
48
49
50
        self.create_service(Trigger, '~/enter', self.enter_srv_callback)  # 进入玩法(enter the game)
        self.create_service(Trigger, '~/exit', self.exit_srv_callback)  # 退出玩法(exit the game)
        self.create_service(SetInt64, '~/set_running', self.set_running_srv_callback)  # 开启玩法(start the game)
        Heart(self, self.name + '/heartbeat', 5, lambda _: self.exit_srv_callback(request=Trigger.Request(), response=Trigger.Response()))  # 心跳包(heartbeat package)
        self.create_service(SetFloat64List, '~/set_param', self.set_parameters_srv_callback)  # 参数设置(set parameter)

Enter game: A ROS service named /enter is created with the type Trigger, and the callback function is enter_srv_callback. When this service is called, it will execute the enter_srv_callback function.

Exit game: A ROS service named /exit is created with the type Trigger, and the callback function is exit_srv_callback. When this service is called, it will execute the exit_srv_callback function.

Enable game: A ROS service named /set_running is created with the type SetInt64, and the callback function is set_running_srv_callback. When this service is called, it will execute the set_running_srv_callback function.

Set parameters: A ROS service named /set_param is created with the type SetFloat64List, and the callback function is set_parameters_srv_callback. When this service is called, it will execute the set_parameters_srv_callback function.

(3) Lidar obstacle avoidance

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
                if self.running_mode == 1 and self.timestamp <= time.time():
                    left_nonzero = left_range.nonzero()
                    right_nonzero = right_range.nonzero()
                    left_nonan = np.isfinite(left_range[left_nonzero])
                    right_nonan = np.isfinite(right_range[right_nonzero])
                    min_dist_left_ = left_range[left_nonzero][left_nonan]
                    min_dist_right_ = right_range[right_nonzero][right_nonan]
                    # 取左右最近的距离(Take the nearest distance left and right)
                    if len(min_dist_left_) > 1 and len(min_dist_right_) > 1:
                        min_dist_left = min_dist_left_.min()
                        min_dist_right = min_dist_right_.min()
                        if min_dist_left <= self.threshold and min_dist_right > self.threshold:  # 左侧有障碍(there is obstacle at left)
                            twist.linear.x = self.speed / 6  # 转弯时向前速度尽量小(keep the forward velocity as low as possible when turning)
                            max_angle = math.radians(90)  # 转90度(turn 90 degree)
                            w = self.speed * 6.0  # 转弯速度尽量大(keep the turning speed as high as possible)
                            twist.angular.z = -w
                            if self.last_act != 0 and self.last_act != 1:
                                twist.angular.z = w
                            self.last_act = 1
                            self.mecanum_pub.publish(twist)
                            self.timestamp = time.time() + (max_angle / w / 2)
                        elif min_dist_left <= self.threshold and min_dist_right <= self.threshold:  # 两侧都有障碍
                            twist.linear.x = self.speed / 6  # 转弯时向前速度尽量小(keep the forward velocity as low as possible when turning)
                            w = self.speed * 6.0  # 转弯速度尽量大(keep the turning speed as high as possible)
                            twist.angular.z = w
                            self.last_act = 3
                            self.mecanum_pub.publish(twist)
                            # 转180度
                            self.timestamp = time.time() + (math.radians(180) / w / 2)
                        elif min_dist_left > self.threshold and min_dist_right <= self.threshold:  # 右侧有障碍(there is obstacles at right)
                            twist.linear.x = self.speed / 6  # 转弯时向前速度尽量小(keep the forward velocity as low as possible when turning)
                            max_angle = math.radians(90)  # 转90度 turn 90 degrees
                            w = self.speed * 6.0  # 转弯速度尽量大(keep the turning speed as high as possible)
                            twist.angular.z = w
                            if self.last_act != 0 and self.last_act != 2:
                                twist.angular.z = -w
                            self.last_act = 2
                            self.mecanum_pub.publish(twist)
                            self.timestamp = time.time() + (max_angle / w / 2)
                        else:  # 没有障碍(none obstacle)

The code detects the minimum distances on the left and right sides of the robot, stored in variables min_dist_left and min_dist_right, respectively. Based on the relationship between the minimum distances and a threshold value self.threshold, the code determines the presence of obstacles on either side. If there is an obstacle on the left side, the robot will turn right by 90 degrees. If there are obstacles on both sides, the robot will turn around by 180 degrees. If there is an obstacle on the right side, the robot will turn left by 90 degrees. If there is no obstacle, the robot will continue moving forward.

To avoid repetitive turns, the code keeps track of the last action performed by the robot in the variable self.last_act.

4.4 Lidar Following

For instructions on app connection, please consult the tutorial located in “1.Getting Ready->1.6 APP Control”.

4.4.1 Enable Game

  • Initiate Lidar Game through App

(1) Connect the robot to “WonderPi” app.

(2) Click-on Lidar to navigate to the game interface.

(3) Switch on “Lidar following” button to start the game.

  • Initiate Lidar Game Using Command

(1) Start the robot, and access the robot system desktop using VNC according to the tutorial saved in “2. Set Development Environment->2.1 VNC Installation and Connection”.

(2) Click-on to open the command line terminal.

(3) Execute the command and press Enter to deactivate the automatic start service for the app.

~/.stop_ros.sh

(4) Run the command to enable the local services for app-related game and chassis control services.

ros2 launch app lidar_node.launch.py debug:=true

(5) Open a new terminal, and execute the command. Then, press Enter to start the Lidar game.

ros2 service call /lidar_app/enter std_srvs/srv/Trigger {}

Note

The robot’s performance initiated by the app and command is identical.

(6) Enter the command and hit Enter to launch Lidar following game.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 2}"

(7) If you want to exit the game, enter the command and press “Enter”.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 0}"

Note

The game will continue to run under the current Raspberry Pi power-on state if not exited. To avoid excessive use of the Raspberry Pi’s operating memory, please follow the above instructions to close the current game before executing other games.

(8) If you need to terminate the program, press short-cut “Ctrl+C” on the terminal opened in steps (4) and (5).

After experiencing the Lidar game, you can activate the app service either by using a command or restarting the robot. If the app service is not activated, related app functions will be disabled. In the case of a robot restart, the app service will start automatically.

Click and enter the command. Press enter to start the app, and wait for the buzzer to beep. Please enter the command in the system path, not in the Docker container.

sudo systemctl restart start_node.service

4.4.2 Program Outcome

Let’s use a book as the object to be detected. When using the Lidar following function, it’s important to ensure that the object to be detected is higher than the scanning height of the Lidar mounted on the MentorPi. This will enable the Lidar to effectively scan the position information of the object. Once the position information is obtained, the MentorPi will adjust its position to maintain a distance of around 0.35m between itself and the obstacle.

4.4.3 Program Analysis

  • Launch File

The launch file is located in: /home/ubuntu/ros2_ws/src/app/launch/lidar_node.launch.py

(1) Set the storage path

Retrieve the paths for the two packages: peripherals and controller.

14
15
16
17
18
19
    if compiled == 'True':
        controller_package_path = get_package_share_directory('controller')
        peripherals_package_path = get_package_share_directory('peripherals')
    else:
        controller_package_path = '/home/ubuntu/ros2_ws/src/driver/controller'
        peripherals_package_path = '/home/ubuntu/ros2_ws/src/peripherals'

(2) Initiate other Launch files

21
22
23
24
25
26
27
28
29
30
31
32
    lidar_controller_node = GroupAction([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(peripherals_package_path, 'launch/lidar.launch.py')),
            condition=IfCondition(debug),
            ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(controller_package_path, 'launch/controller.launch.py')),
            condition=IfCondition(debug),
            ),

lidar.launch.py Lidar launch

controller.launch.py Motion control launch

(3) Initiate Node

34
35
36
37
38
39
        Node(
            package='app',
            executable='lidar_controller',
            output='screen',
            ),
    ])

Enable Lidar game node.

  • Source Code File

The source code of the program is saved in: /home/ubuntu/ros2_ws/src/app/app/lidar_controller.py

The process logic of the program based on the implemented effect is shown in the diagram:

(1) Initialization

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class LidarController(Node):
    def __init__(self, name):
        rclpy.init()
        super().__init__(name)

        self.name = name
        self.running_mode = 0
        self.threshold = 0.6  # meters
        self.scan_angle = math.radians(360)  # radians
        self.speed = 0.2
        self.last_act = 0
        self.timestamp = 0
        self.angle_data = []
        # pid参数
        self.pid_yaw = pid.PID(1.6, 0, 0.16)
        self.pid_dist = pid.PID(1.7, 0, 0.16)
        self.lock = threading.RLock()
        self.lidar_sub = None
        self.lidar_type = os.environ.get('LIDAR_TYPE')
        self.machine_type = os.environ.get('MACHINE_TYPE')

self.name: stores the incoming name as the property of the object.

Other attributes include parameters for controlling the Lidar, such as operating mode, threshold, scanning angle, and speed.

self.lock: creates a thread lock for safe access in a multi-threaded environment.

(2) Create ROS service

25
26
27
28
        self.create_service(Trigger, '~/enter', self.enter_srv_callback)  # 进入玩法(enter the game)
        self.create_service(Trigger, '~/exit', self.exit_srv_callback)  # 退出玩法(exit the game)
        self.create_service(SetInt64, '~/set_running', self.set_running_srv_callback)  # 开启玩法(start the game)
        Heart(self, self.name + '/heartbeat', 5, lambda _: self.exit_srv_callback(request=Trigger.Request(), response=Trigger.Response()))  # 心跳包(heartbeat package)

Enter game: A ROS service named /enter is created with the type Trigger, and the callback function is enter_srv_callback. When this service is called, it will execute the enter_srv_callback function.

Exit game: A ROS service named /exit is created with the type Trigger, and the callback function is exit_srv_callback. When this service is called, it will execute the exit_srv_callback function.

Enable game: A ROS service named /set_running is created with the type SetInt64, and the callback function is set_running_srv_callback. When this service is called, it will execute the set_running_srv_callback function.

Set parameters: A ROS service named /set_param is created with the type SetFloat64List, and the callback function is set_parameters_srv_callback. When this service is called, it will execute the set_parameters_srv_callback function.

(3) Lidar following

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
                    # 拼合距离数据, 从右半侧逆时针到左半侧(the merged distance data from right half counterclockwise to the left half)
                    ranges = np.append(right_range[::-1], left_range)
                    self.get_logger().info(str(ranges))
                    nonzero = ranges.nonzero()
                    nonan = np.isfinite(ranges[nonzero])
                    dist_ = ranges[nonzero][nonan]
                    if len(dist_) > 0:
                        dist = dist_.min()
                        min_index = list(ranges).index(dist)
                        angle = -angle + lidar_data.angle_increment * min_index  # 计算最小值对应的角度(calculate the angle corresponding to the minimum value)
                        # self.get_logger().info(str(dist_))
                        # self.get_logger().info(str([dist, angle]))
                        if dist < self.threshold and abs(math.degrees(angle)) > 5:  # 控制左右(control the left and right)
                            if self.lidar_type != 'G4':
                                self.pid_yaw.update(-angle)
                                twist.angular.z = common.set_range(self.pid_yaw.output, -self.speed * 6.0, self.speed * 6.0) 
                            elif self.lidar_type == 'G4':
                                self.pid_yaw.update(angle)
                                twist.angular.z = -common.set_range(self.pid_yaw.output, -self.speed * 6.0, self.speed * 6.0) 
                        else:
                            self.pid_yaw.clear()

                        if dist < self.threshold and abs(0.2 - dist) > 0.02:  # 控制前后(control the front and back)
                            self.pid_dist.update(self.threshold / 2 - dist)
                            twist.linear.x = common.set_range(self.pid_dist.output, -self.speed, self.speed)
                        else:
                            self.pid_dist.clear()
                        if abs(twist.angular.z) < 0.008:
                            twist.angular.z = 0.0
                        if abs(twist.linear.x) < 0.05:
                            twist.linear.x = 0.0
                        self.mecanum_pub.publish(twist)

The code concatenates the distance data from the left and right sides of the robot, obtained from the Lidar, into an array ranges to form a circular scan. It then calculates the minimum distance value “dist” and its corresponding angle angle from the ranges.

The dist is checked if it is less than a threshold value. If it is and the angle deviates from the forward direction by more than 5 degrees, the robot will rotate to avoid the obstacle. The angular velocity is controlled using a PID controller. If dist is less than the threshold and the robot is close to a distance of 0.2m in the forward direction, the robot will stop or move forward using a PID controller for the linear velocity. The control commands are published as a Twist message to the robot to execute the desired actions.

4.5 Lidar Guarding

For instructions on app connection, please consult the tutorial located in “1. Getting Ready->1.6 APP Control”.

4.5.1 Enable Game

Note

The MentorPi of Ackerman version does not support Lidar guarding function.

  • Initiate Lidar Game through App

(1) Connect the robot to “WondePi” app.

(2) Click-on Lidar to navigate to the game interface.

(3) Switch on “Lidar guarding” button to start the game.

  • Initiate Lidar Game Using Command

(1) Start the robot, and access the robot system desktop using VNC according to the tutorial saved in “2. Set Development Environment->2.1 VNC Installation and Connection”.

(2) Click-on to open the command line terminal.

(3) Execute the command and press Enter to deactivate the automatic start service for the app.

~/.stop_ros.sh

(4) Run the command to enable the local services for app-related game and chassis control services.

ros2 launch app lidar_node.launch.py debug:=true

(5) Open a new terminal, and execute the command. Then, press Enter to start the Lidar game.

ros2 service call /lidar_app/enter std_srvs/srv/Trigger {}

Note

The robot’s performance initiated by the app and command is identical.

(6) Enter the command and hit Enter to launch Lidar guarding game.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 3}"

(7) If you want to exit the game, enter the command and press “Enter”.

ros2 service call /lidar_app/set_running interfaces/srv/SetInt64 "{data: 0}"

Note

The game will continue to run under the current Raspberry Pi power-on state if not exited. To avoid excessive use of the Raspberry Pi’s operating memory, please follow the above instructions to close the current game before executing other games.

(8) If you need to terminate the program, press short-cut “Ctrl+C” on the terminal opened in steps (4) and (5).

After experiencing the Lidar game, you can activate the app service either by using a command or restarting the robot. If the app service is not activated, related app functions will be disabled. In the case of a robot restart, the app service will start automatically.

Click and enter the command. Press enter to start the app, and wait for the buzzer to beep. Please enter the command in the system path, not in the Docker container.

sudo systemctl restart start_node.service

4.5.2 Program Outcome

Let’s use a book as the object to be detected. When using the Lidar following function, it’s important to ensure that the object to be detected is higher than the scanning height of the Lidar mounted on the MentorPi. This will enable the Lidar to effectively scan the position information of the object. Once the position information is obtained, the MentorPi will adjust its orientation to face towards the obstacle. It means that the camera will face directly towards the obstacle.

4.5.3 Program Analysis

  • Launch File

The launch file is located in: /home/ubuntu/ros2_ws/src/app/launch/lidar_node.launch.py

(1) Set the storage path

Retrieve the paths for the two packages: peripherals and controller.

14
15
16
17
18
19
    if compiled == 'True':
        controller_package_path = get_package_share_directory('controller')
        peripherals_package_path = get_package_share_directory('peripherals')
    else:
        controller_package_path = '/home/ubuntu/ros2_ws/src/driver/controller'
        peripherals_package_path = '/home/ubuntu/ros2_ws/src/peripherals'

(2) Initiate other Launch files

21
22
23
24
25
26
27
28
29
30
31
32
    lidar_controller_node = GroupAction([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(peripherals_package_path, 'launch/lidar.launch.py')),
            condition=IfCondition(debug),
            ),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(controller_package_path, 'launch/controller.launch.py')),
            condition=IfCondition(debug),
            ),

lidar.launch.py Lidar launch

controller.launch.py Motion control launch

(3) Initiate Node

34
35
36
37
38
39
        Node(
            package='app',
            executable='lidar_controller',
            output='screen',
            ),
    ])

Enable Lidar game node.

  • Source Code File

The source code of the program is saved in:/home/ubuntu/ros2_ws/src/app/app/lidar_controller.py

The process logic of the program based on the implemented effect is shown in the diagram:

(1) Initialization

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class LidarController(Node):
    def __init__(self, name):
        rclpy.init()
        super().__init__(name)

        self.name = name
        self.running_mode = 0
        self.threshold = 0.6  # meters
        self.scan_angle = math.radians(360)  # radians
        self.speed = 0.2
        self.last_act = 0
        self.timestamp = 0
        self.angle_data = []
        # pid参数
        self.pid_yaw = pid.PID(1.6, 0, 0.16)
        self.pid_dist = pid.PID(1.7, 0, 0.16)
        self.lock = threading.RLock()
        self.lidar_sub = None
        self.lidar_type = os.environ.get('LIDAR_TYPE')
        self.machine_type = os.environ.get('MACHINE_TYPE')

self.name: stores the incoming name as the property of the object.

Other attributes include parameters for controlling the Lidar, such as operating mode, threshold, scanning angle, and speed.

self.lock: creates a thread lock for safe access in a multi-threaded environment.

(2) Create ROS service

46
47
48
49
50
        self.create_service(Trigger, '~/enter', self.enter_srv_callback)  # 进入玩法(enter the game)
        self.create_service(Trigger, '~/exit', self.exit_srv_callback)  # 退出玩法(exit the game)
        self.create_service(SetInt64, '~/set_running', self.set_running_srv_callback)  # 开启玩法(start the game)
        Heart(self, self.name + '/heartbeat', 5, lambda _: self.exit_srv_callback(request=Trigger.Request(), response=Trigger.Response()))  # 心跳包(heartbeat package)
        self.create_service(SetFloat64List, '~/set_param', self.set_parameters_srv_callback)  # 参数设置(set parameter)

Enter game: A ROS service named /enter is created with the type Trigger, and the callback function is enter_srv_callback. When this service is called, it will execute the enter_srv_callback function.

Exit game: A ROS service named /exit is created with the type Trigger, and the callback function is exit_srv_callback. When this service is called, it will execute the exit_srv_callback function.

Enable game: A ROS service named /set_running is created with the type SetInt64, and the callback function is set_running_srv_callback. When this service is called, it will execute the set_running_srv_callback function.

Set parameters: A ROS service named /set_param is created with the type SetFloat64List, and the callback function is set_parameters_srv_callback. When this service is called, it will execute the set_parameters_srv_callback function.

(3) Lidar guarding

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
                elif self.running_mode == 3:
                    # 拼合距离数据, 从右半侧逆时针到左半侧(the merged distance data from right half counterclockwise to the left half)
                    ranges = np.append(right_range[::-1], left_range)
                    nonzero = ranges.nonzero()
                    nonan = np.isfinite(ranges[nonzero])
                    dist_ = ranges[nonzero][nonan]
                    if len(dist_) > 1:
                        dist = dist_.min()
                        min_index = list(ranges).index(dist)
                        
                        angle = -angle + lidar_data.angle_increment * min_index  # 计算最小值对应的角度(calculate the angle corresponding to the minimum value)
                        if dist < self.threshold and abs(math.degrees(angle)) > 5:  # 控制左右(control the left and right)
                            if self.lidar_type != 'G4':
                                self.pid_yaw.update(-angle)
                                twist.angular.z = common.set_range(self.pid_yaw.output, -self.speed * 6.0, self.speed * 6.0)
                            elif self.lidar_type == 'G4':
                                self.pid_yaw.update(angle)
                                twist.angular.z = -common.set_range(self.pid_yaw.output, -self.speed * 6.0, self.speed * 6.0) 
                        else:
                            self.pid_yaw.clear()
                        if abs(twist.angular.z) < 0.008:
                            twist.angular.z = 0.0
                        self.mecanum_pub.publish(twist)

The code concatenates the distance data from the left and right sides of the robot, obtained from the Lidar, into an array ranges to form a circular scan. It then calculates the minimum distance value dist and its corresponding angle angle from the ranges array.

If dist is less than a threshold value and the angle deviates from the forward direction by more than 5 degrees, the Yaw axis PID controller will be activated. Based on the output of the PID controller, the code calculates the turning angular velocity twist.angular.z. The control commands are published as a Twist message to the robot to execute the desired actions.