8. sonar.py ModuleΒΆ

In the previous project, we learned how the sonar scanner detects obstacles. That is the servo is turned from left to right and the ultrasonic module detects every specific angle, then several sets of distance values are output at once.

However, this kind of data is not very convenient for calculation, so in practice, we can set a threshold value, and then compare the detected distance against this threshold value, and then output a 0 or 1.

In this way, we encapsulate the sonar scanning code into a library and then add more calculations to it, so that it can be easily imported for use in the project.

Note

The final encapsulated library sonar.py has been saved in pico_4wd_car-v2.0\libs, which may differ from the ones shown in the course, so please refer to the file under libs path when using it.

You can learn how sonar.py is encapsulated in the following steps.

1. Determine if there are obstacles

  • Most of the time, the car only needs to know whether there are obstacles in all directions.

  • So here set a distance threshold with the variable SONAR_REFERENCE.

  • And then create the function get_sonar_status() to determine if the distance is greater than the threshold, then output 1, otherwise output 0.

  • Then output the processed data at once, and you get array like this [1, 0, 0, 1, 1, 1, 1].

from servo import Servo
from ultrasonic import Ultrasonic
import time

servo = Servo(18)
ultrasonic = Ultrasonic(6, 7)

sonar_angle = 0
sonar_step = 30

SONAR_MAX_ANGLE = 90
SONAR_MIN_ANGLE = -90
SONAR_REFERENCE = 20

sonar_data =[]
for i in range((SONAR_MAX_ANGLE-SONAR_MIN_ANGLE)/sonar_step+1):
    sonar_data.append(None)

def get_distance_at(angle):
    global sonar_angle
    sonar_angle = angle
    servo.set_angle(sonar_angle)
    #time.sleep(0.04)
    distance = ultrasonic.get_distance()
    if distance < 0:
        return -1
    else:
        return distance

def sonar_move():
    global sonar_angle, sonar_step
    if sonar_angle >= SONAR_MAX_ANGLE:
        sonar_angle = SONAR_MAX_ANGLE
        sonar_step = -abs(sonar_step)
    elif sonar_angle <= SONAR_MIN_ANGLE:
        sonar_angle = SONAR_MIN_ANGLE
        sonar_step = abs(sonar_step)
    sonar_angle += sonar_step

def get_sonar_status(distance):
    if distance > SONAR_REFERENCE or distance < 0:
        return 1
    else:
        return 0

def mapping(x, in_min, in_max, out_min, out_max):
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

def sonar_scan():
    global sonar_data
    sonar_move()
    distance = get_distance_at(sonar_angle)
    index= int(mapping(sonar_angle, SONAR_MIN_ANGLE, SONAR_MAX_ANGLE, 0, len(sonar_data)-1))
    status=get_sonar_status(distance)
    sonar_data[index]=status
    return sonar_data

while True:
    print(sonar_scan())
    time.sleep(0.1)

2. Get complete data before judging

Additionally, if we use sonar_data directly for obstacle determination, the data on the left becomes an interference item when the left side obstacle disappears and the sonar scans the right side.

It makes more sense to determine an obstacle after a sonar cycle has been scanned and complete data has been collected.

...
...

def get_distance_at(angle):
    ...

def sonar_move():
    ...
def get_sonar_status(distance):
    ...
def mapping(x, in_min, in_max, out_min, out_max):
    ...

def sonar_scan():
    global sonar_data
    sonar_move()
    distance = get_distance_at(sonar_angle)
    index=int(mapping(sonar_angle, SONAR_MIN_ANGLE, SONAR_MAX_ANGLE, 0, len(sonar_data)-1))
    status=get_sonar_status(distance)
    sonar_data[index]=status
    if (index == 0 or index == len(sonar_data)-1) and None not in sonar_data:
        return sonar_angle,distance,sonar_data
    else:
        return sonar_angle,distance,status

while True:
    _,_,result = sonar_scan()
    if type(result) is not int:
        print(result)
    time.sleep(0.1)

3. Further optimization

In order to be compatible with more complex programs, we created two more functions to modify the rotation rules and distance determination of the sonar.

...
...

def get_distance_at(angle):
    ...

def sonar_move():
    ...
def get_sonar_status(distance):
    ...
def mapping(x, in_min, in_max, out_min, out_max):
    ...
def sonar_scan():
    ...

def set_sonar_scan_config(scan_range=None,step=None):
    global SONAR_MAX_ANGLE, SONAR_MIN_ANGLE, sonar_angle, sonar_step, sonar_data

    # update changed
    item = 0
    if scan_range is None or scan_range is SONAR_MAX_ANGLE-SONAR_MIN_ANGLE:
        item+=1
    else:
        SONAR_MAX_ANGLE = int(scan_range / 2)
        SONAR_MIN_ANGLE = SONAR_MAX_ANGLE-scan_range
    if step is None or abs(sonar_step) is abs(step):
        item+=1
    else:
        sonar_step=int(step)
    if item is 2: # if nothing change, return
        return

    # re-create the data list
    sonar_data =[]
    for i in range(scan_range/abs(sonar_step) +1):
        sonar_data.append(None)

    sonar_angle=0
    servo.set_angle(sonar_angle)

def set_SONAR_REFERENCE(ref):
    global SONAR_REFERENCE
    SONAR_REFERENCE = int(ref)


if __name__ == '__main__':
    try:
        set_sonar_scan_config(180,30)
        set_SONAR_REFERENCE(20)
        while True:
            _,_,status = sonar_scan()
            if type(status) is not int:
                print(status)
            time.sleep(0.1)
    finally:
        servo.set_angle(0)