.. _py_avoid:
4. 避障功能
=====================
在本项目中,PiCrawler 将使用超声波模块检测前方的障碍物。
当检测到障碍物时,PiCrawler 会发送信号,并寻找其他方向继续前进。
**运行代码**
.. raw:: html
.. code-block::
cd ~/picrawler/examples
sudo python3 4_avoid.py
当程序启动时,PiCrawler 会站立起来。
它会持续使用超声波传感器测量距离,
并在终端中打印测量到的数值。
如果在 15 cm 范围内检测到障碍物:
- 会播放警告声音。
- 机器人会进行一次小幅度左转。
如果前方路径是畅通的:
- 机器人会向前移动。
机器人会持续自动避障运行,直到你按下 Ctrl+C。
在程序退出之前,机器人会安全地坐下。
**代码**
.. note::
你可以对下面的代码进行 **Modify/Reset/Copy/Run/Stop** 操作。但在此之前,需要先进入源码路径,如 ``picrawler\examples``。修改代码后,可以直接运行查看效果。
.. raw:: html
.. code-block:: python
from picrawler import Picrawler
from robot_hat import Music, Ultrasonic, Pin
import time
import signal
music = Music()
crawler = Picrawler()
sonar = Ultrasonic(Pin("D2"), Pin("D3")) # Ultrasonic trigger/echo pins
music.music_set_volume(100) # Set speaker volume
alert_distance = 15 # Obstacle warning distance (cm)
speed = 80 # Movement speed
# ----------------------------
# Add hardware timeout to sonar.read()
# Prevent program from freezing
# ----------------------------
class Timeout(Exception):
pass
def _alarm_handler(signum, frame):
raise Timeout()
signal.signal(signal.SIGALRM, _alarm_handler)
# Read distance once with timeout protection
def safe_read_once(timeout_s=1):
try:
signal.alarm(timeout_s)
d = sonar.read()
signal.alarm(0)
return d
except Timeout:
signal.alarm(0)
return None
except Exception:
signal.alarm(0)
return None
# Read multiple times and return median value (anti-noise)
def read_distance_filtered(n=5, gap=0.03, timeout_s=1):
vals = []
for _ in range(n):
d = safe_read_once(timeout_s=timeout_s)
if d is not None and d > 0:
vals.append(d)
time.sleep(gap)
if not vals:
return None
vals.sort()
return vals[len(vals)//2] # Median filter
def main():
distance = read_distance_filtered(n=5, gap=0.03, timeout_s=1)
print("distance:", distance)
if distance is None:
time.sleep(0.15) # Wait if read failed
return
if distance <= alert_distance:
# Obstacle detected → play sound and turn
try:
music.sound_play_threading('./sounds/sign.wav', volume=100)
except Exception as e:
print("sound error:", e)
crawler.do_action('turn left angle', 1, speed)
time.sleep(0.5) # Quiet window after movement
else:
# Path clear → move forward
crawler.do_action('forward', 1, speed)
time.sleep(0.4)
if __name__ == "__main__":
try:
crawler.do_step('stand', 40) # Stand before starting
time.sleep(1.0)
while True:
main()
except KeyboardInterrupt:
print("\nStop.")
finally:
try:
crawler.do_step('sit', 40) # Sit before exit
time.sleep(1.0)
except Exception:
pass
**工作原理**
#. 初始化模块
.. code-block:: python
music = Music()
crawler = Picrawler()
sonar = Ultrasonic(Pin("D2"), Pin("D3"))
music.music_set_volume(100)
alert_distance = 15
speed = 80
该代码块初始化三个主要模块:
- ``music``:用于控制声音播放。
- ``crawler``:用于控制 PiCrawler 的运动。
- ``sonar``:通过超声波传感器读取距离。
同时还设置了扬声器音量、障碍物检测阈值(单位:厘米),
以及机器人的运动速度。
#. 超时保护模块(防止 sonar.read() 卡死)
.. code-block:: python
class Timeout(Exception):
pass
def _alarm_handler(signum, frame):
raise Timeout()
signal.signal(signal.SIGALRM, _alarm_handler)
超声波驱动在等待回波信号时可能会阻塞程序。
该代码块安装了一个信号处理器,使程序可以中断
卡住的 ``sonar.read()`` 调用,从而保证程序继续运行。
#. 函数:safe_read_once()
.. code-block:: python
def safe_read_once(timeout_s=1):
try:
signal.alarm(timeout_s)
d = sonar.read()
signal.alarm(0)
return d
except Timeout:
signal.alarm(0)
return None
except Exception:
signal.alarm(0)
return None
该函数在带有超时保护的情况下读取一次超声波距离。
- 如果读取成功,则返回距离值。
- 如果读取超时或发生错误,则返回 ``None``,避免程序卡死。
#. 函数:read_distance_filtered()
.. code-block:: python
def read_distance_filtered(n=5, gap=0.03, timeout_s=1):
vals = []
for _ in range(n):
d = safe_read_once(timeout_s=timeout_s)
if d is not None and d > 0:
vals.append(d)
time.sleep(gap)
if not vals:
return None
vals.sort()
return vals[len(vals)//2]
该函数通过多次采样来提高测量可靠性:
- 无效数据(``None`` 或 ``<= 0``)会被忽略。
- 将剩余的有效数据排序。
- 返回中位数作为最终距离值,从而减少噪声影响。
#. 函数:main()(核心决策与动作)
.. code-block:: python
def main():
distance = read_distance_filtered(...)
if distance is None:
return
if distance <= alert_distance:
music.sound_play_threading(...)
crawler.do_action('turn left angle', 1, speed)
else:
crawler.do_action('forward', 1, speed)
这是主要的控制逻辑:
- 读取经过过滤的距离值。
- 如果读取失败,则跳过本次循环。
- 如果检测到的障碍物距离小于 ``alert_distance``,
则播放警告声音并向左转。
- 否则机器人向前移动。
#. 程序入口模块(循环运行 + 安全退出)
.. code-block:: python
if __name__ == "__main__":
try:
crawler.do_step('stand', 40)
while True:
main()
except KeyboardInterrupt:
print("\nStop.")
finally:
crawler.do_step('sit', 40)
该代码块控制程序的整体运行流程:
- 程序开始前,机器人先站立。
- 在无限循环中不断执行 ``main()``。
- 按下 Ctrl+C 可以终止程序。
- 程序退出前,机器人会安全地坐下。