# 4. Obstacle Avoid¶

Let Pico-4wd do a challenging task: automatically avoid obstacles! When an obstacle is detected, instead of simply backing up, the sonar scans the surrounding area and finds the widest way to move forward.

This project is an advanced version of the previous project 3. Objects Follow. The previous project was to find the direction where the obstacle is, while here it is to find the direction where there is no obstacle. To get a better understanding of this project, it is recommended that you finish the previous project first.

Note

• The complete script `project_4_avoid.py` is in the path `pico_4wd_car\examples\funny_projects`.

• In order to allow the car to move on the ground without the USB cable connected, you need to save this script as `main.py` to Raspberry Pi Pico, see 5. Run Script Offline(Important) for a tutorial.

Below are the steps to implement the obstacle avoidance function, and you can copy them into Thonny to run them.

1. Sonar scanning

• Let the car scan the area between -30° and 30° in front of it. Let the ultrasonic module read the values every 10° and then print out at once.

• Then process the scan results. In contrast to the previous project, here the scan data is separated by `0`, resulting in an area with no obstacles.

• Data length is divided into left, middle, and right. Locate the center point of the region without obstacles and return `"forward"` if it is there.

• If both have obstacles, then return `"left"`.

```import sonar as sonar
import motors as car
import time

def get_dir(data,split_str='0'):

# get scan status of 0, 1
data = [str(i) for i in data]
data = "".join(data)

# Split 0, leaves the free path
paths = data.split(split_str)

# Find the max path
max_paths=max(paths)

# If no wide enough path
if len(max_paths)<2:
return "left"

# Calculate the direction of the widest one
position = data.index(max_paths) # find the widest path position
position += (len(max_paths)-1)/2 # find the middle of the widest path

# Divide the scanning area into three pieces and mark the widest one
if position < len(data) / 3:
return "left"
elif position > 2 * len(data) / 3:
return "right"
else:
return "forward"

try:
sonar.set_sonar_scan_config(scan_range=60, step=10)
sonar.set_sonar_reference(30)
while True:
_, _, sonar_data = sonar.sonar_scan()
# sonar_data: 0 is block, 1 is pass
time.sleep(0.04)

# If sonar data return a int, means scan not finished, and the int is current angle status
if isinstance(sonar_data, int):
continue # only list can go on

direction = get_dir(sonar_data,split_str='0')
print(sonar_data)
print("The Car Should Go: ", direction)

finally:
pass
```

2. Avoiding obstacles

• Move according to the result returned after processing. For example, if `"left"` is returned, make the car turn left.

• While the car is turning, rotate the sonar scanner in the opposite direction until no obstacle is detected.

• For example, if it is turning left, the sonar scanner will turn to the right.

```import sonar as sonar
import motors as car
import time

def get_dir(data,split_str='0'):

# get scan status of 0, 1
data = [str(i) for i in data]
data = "".join(data)

# Split 0, leaves the free path
paths = data.split(split_str)

# Find the max path
max_paths=max(paths)

# If no wide enough path
if len(max_paths)<4:
return "left"

# Calculate the direction of the widest one
position = data.index(max_paths) # find the widest path position
position += (len(max_paths)-1)/2 # find the middle of the widest path

# Divide the scanning area into three pieces and mark the widest one
if position < len(data) / 3:
return "left"
elif position > 2 * len(data) / 3:
return "right"
else:
return "forward"

def running(direction,power):
if direction is "left":
sonar.get_distance_at(20) # face right
time.sleep(0.2)
car.move("left", power*2)
while True:
distance = sonar.get_distance_at(20) # face right
status = sonar.get_sonar_status(distance)
if status is 1: # right position is pass
break
car.move("stop")
elif direction is "right":
sonar.get_distance_at(-20) # face left
time.sleep(0.2)
car.move("right", power*2)
while True:
distance = sonar.get_distance_at(-20) # face left
status = sonar.get_sonar_status(distance)
if status is 1: # left position is pass
break
car.move("stop")
else:
# pass
car.move("forward",power)

try:
MOTOR_POWER = 30
sonar.set_sonar_scan_config(scan_range=60, step=10)
sonar.set_sonar_reference(30)
while True:
_, _, sonar_data = sonar.sonar_scan()
# sonar_data: 0 is block, 1 is pass
time.sleep(0.04)

# If sonar data return a int, means scan not finished, and the int is current angle status
if isinstance(sonar_data, int):
continue # only list can go on
direction = get_dir(sonar_data,split_str='0')
running(direction, MOTOR_POWER)

finally:
car.move("stop")
```

3. Reduce scanning angle

The car only needs to detect if there is an obstacle in front of it while moving forward. However, when it encounters an obstacle, it should stop and find a new route. Thus, the sonar scanner’s search range should be changed from 60° to 180°.

```import sonar as sonar
import motors as car
import time

def get_dir(data,split_str='0'):

# get scan status of 0, 1
data = [str(i) for i in data]
data = "".join(data)

# Split 0, leaves the free path
paths = data.split(split_str)

# Find the max path
max_paths=max(paths)

# If no wide enough path
if len(max_paths)<4:
return "left"

# Calculate the direction of the widest one
position = data.index(max_paths) # find the widest path position
position += (len(max_paths)-1)/2 # find the middle of the widest path

# Divide the scanning area into three pieces and mark the widest one
if position < len(data) / 3:
return "left"
elif position > 2 * len(data) / 3:
return "right"
else:
return "forward"

def running(direction,power):
if direction is "left":
sonar.get_distance_at(20) # face right
time.sleep(0.2)
car.move("left", power*2)
while True:
distance = sonar.get_distance_at(20) # face right
status = sonar.get_sonar_status(distance)
if status is 1: # right position is pass
break
car.move("stop")
elif direction is "right":
sonar.get_distance_at(-20) # face left
time.sleep(0.2)
car.move("right", power*2)
while True:
distance = sonar.get_distance_at(-20) # face left
status = sonar.get_sonar_status(distance)
if status is 1: # left position is pass
break
car.move("stop")
else:
# pass
car.move("forward",power)

try:
MOTOR_POWER = 30
SCAN_RANGE_PASS = 60
SCAN_RANGE_BLOCK = 180
SCAN_STEP = 10
status = "pass"
sonar.set_sonar_scan_config(scan_range=SCAN_RANGE_PASS, step=SCAN_STEP)
sonar.set_sonar_reference(30)
while True:
_, _, sonar_data = sonar.sonar_scan()
# sonar_data: 0 is block, 1 is pass
time.sleep(0.04)

# If sonar data return a int, means scan not finished, and the int is current angle status
if isinstance(sonar_data, int):
if sonar_data is 0 and status is "pass": #If it finds an obstacle
status = "block"
car.move("stop")
sonar.set_sonar_scan_config(SCAN_RANGE_BLOCK) # change scan range to 180 and re-scan
continue # only list can go on
direction = get_dir(sonar_data,split_str='0')
running(direction, MOTOR_POWER)
status = "pass" # find a passable way
sonar.set_sonar_scan_config(SCAN_RANGE_PASS) # change scan range to 60 for go forward

finally:
car.move("stop")
```