Python 用 Python 控制机器人车详解

第 13 章介绍树莓派机器人车时,我们建造了我们的 T.A.R.A.S 机器人车。在本章末尾,我们讨论了如何通过代码控制 T.A.R.A.S。在本章中,我们将开始编写代码来实现这一点

我们将从编写简单的 Python 代码开始,然后利用 GPIO Zero 库使车轮向前移动,移动固定摄像头的伺服电机,并点亮机器人车后部的 LED。

然后,我们将使用类来组织代码,然后进一步增强代码,因为我们将 T.A.R.A.S 派遣到一个秘密的安全任务中。

本章将介绍以下主题:

如果您没有阅读前几章中的项目就跳到了本章,那么让我概括一下您完成以下项目所需的技能。当然,我们必须知道如何绕过 Raspbian 操作系统,才能找到我们的集成开发环境IDE

After you have finished programming T.A.R.A.S, you may be inclined to take your new skills and compete against other people building Raspberry Pi robots. Pi Wars (https://piwars.org/) is such a place to do just that. Pi Wars is an international robotics competition that takes place in Cambridge in the UK. Up to 76 teams compete in challenge-based robotics competitions over the course of a weekend. Even though it is called Pi Wars, you can rest assured that you will not come back with a box of broken parts, as each of the competitions are non-destructive challenges. Check out https://piwars.org/, or search for Pi Wars videos on YouTube for more information.

此外,还需要掌握 Python 的实用知识,因为我们将在本章中用 Python 编写所有代码。由于我尽可能地喜欢使用面向对象的方法,因此一些关于面向对象编程OOP的知识也将帮助您从本章中受益更多。

在本章中,我们将安排 T.A.R.A.S 围着桌子跳舞并拍照。本章中的项目需要几个小时才能完成。

为完成本项目,需要以下各项:

  • Raspberry Pi 3 型(2015 型或更新型)
  • USB 电源
  • 计算机显示器
  • USB 键盘
  • USB 鼠标
  • 一套完整的 T.A.R.A.S 机器人车套件(参见第 13 章介绍树莓派机器人车

在某种程度上,我们的机器人车项目就像是我们在前几章中所做代码的概述。通过使用 Python 和惊人的 GPIO Zero 库,我们能够从 GPIO 读取感官数据,并通过写入 GPIO 引脚控制输出设备。在以下步骤中,我们将从非常简单的 Python 代码和 GPIO Zero 库开始。如果您已经完成了本书中早期的一些项目,那么代码对您来说似乎非常熟悉。

让我们看看能不能让 T.A.R.A.s.移动一点。我们将首先编写一些基本代码来来回移动机器人车:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码:
from gpiozero import Robot
from time import sleep

robot = Robot(left=(5,6), right=(22,27))
robot.forward(0.2)
sleep(0.5)
robot.backward(0.2)
sleep(0.5)
robot.stop()
  1. 将文件另存为motor-test.py
  2. 运行代码

你应该看到机器人车向前移动0.5秒,然后向后移动相同的时间。如果没有任何阻碍,机器人车应该回到它启动时的位置。代码是相当不言自明的;不过,我们现在将讨论它。

我们首先导入我们需要的库:Robotsleep。之后,我们实例化一个名为robotRobot对象,并将其配置为左侧电机具有56引脚,右侧电机具有2227引脚。之后,我们以0.2的速度向前移动机器人。为了让机器人走得更快,我们增加了这个值。在短暂的延迟后,我们使用robot.backward(0.2)命令将机器人返回到其原始位置。

需要注意的一点是电机的旋转方式,并保持旋转,直到它们被robot.stop()命令停止。

If you found that the motors did not move the way they should, it is because of the wiring. Try experimenting with the wiring and changing the pin numbers for the Robot object (left=(5,6), right=(22,27). It may take a few tries to get it right.

现在我们将测试伺服电机。为此,我们将从右向左平移机器人摄像机支架(机器人头部):

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码:
import Adafruit_PCA9685
from time import sleep

pwm = Adafruit_PCA9685.PCA9685()
servo_min = 150
servo_max = 600

while True:
    pwm.set_pwm(0, 0, servo_min)
    sleep(5)
    pwm.set_pwm(0, 0, servo_max)
    sleep(5)
  1. 将文件另存为servo-test.py
  2. 运行代码

你应该看到机器人头部一直向右移动,等待5秒,然后一直向左移动

在代码中,我们首先导入Adafruit_PCA9685库。导入sleep函数后,我们创建一个PCA9685对象,我们称之为pwm。当然,这是一个使用 Adafruit 代码构建的对象,用于支持 HAT。然后,我们分别使用servo_minservo_max设置伺服可以移动的最小值和最大值

If you are not getting the results you expect, experiment with the servo_min and servo_max values. We touch a bit on servos in Chapter 5, Controlling a Servo with Python.

您可能还记得使用前几章中的 Raspberry Pi 摄像头;特别是,第 9 章构建了一个家庭安全仪表板,我们用它为我们的安全应用程序拍照。因为 T.A.R.A.S 将是我们值得信赖的安全代理,所以它有能力拍照是有道理的。让我们编写一些代码来测试摄像头是否在我们的机器人车上工作:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 输入以下代码:
from picamera import PiCamera
import time

camera = PiCamera()
camera.capture("/home/pi/image-" + time.ctime() + ".png")
  1. 将文件另存为camera-test.py
  2. 运行代码

如果一切都设置正确,您应该会在/home/pi目录中看到一个名为image的图像文件,后跟今天的日期。

我们的安全代理仅限于发出噪音,以提醒我们并吓跑潜在入侵者。在本节中,我们将测试安装在 T.A.R.A.S.上的主动蜂鸣器。

The old British police whistle was one of the earliest and trusted pieces of equipment that police officers of the past had to defend themselves. With its unique sound, the police whistle allowed officers to communicate with each other. Despite the fact that police whistles are no longer in use, its legacy has left its impact on society, such that the term 'Whistle Blower' is used to this day to refer to someone exposing hidden injustices or corruption.

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码:
from gpiozero import Buzzer
from time import sleep

buzzer = Buzzer(12)
buzzer.on()
sleep(5)
buzzer.off()
  1. 将文件另存为buzzer-test.py
  2. 运行代码

在关机之前,您应该听到蜂鸣器的声音5秒。

在 T.A.R.A.S 的背面,我们安装了两个 LED(最好是一个红色和一个绿色)。我们以前使用过简单的 GPIO Zero 库命令来闪烁 LED,所以这对我们来说应该不是一个挑战。让我们更进一步,创建可用于封装 LED 闪烁模式的代码:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny

  2. 单击新建图标创建一个新文件

  3. 键入以下代码:

from gpiozero import LEDBoard
from time import sleep

class TailLights:

    led_lights = LEDBoard(red=21, green=20)

    def __init__(self):
        self.led_lights.on()
        sleep(0.25)
        self.led_lights.off()
        sleep(0.25)

    def blink_red(self, num, duration):
        for x in range(num):
            self.led_lights.red.on()
            sleep(duration)
            self.led_lights.red.off()
            sleep(duration)

    def blink_green(self, num, duration):
        for x in range(num):
            self.led_lights.green.on()
            sleep(duration)
            self.led_lights.green.off()
            sleep(duration)

    def blink_alternating(self, num, duration):
        for x in range(num):
            self.led_lights.red.off()
            self.led_lights.green.on()
            sleep(duration)
            self.led_lights.red.on()
            self.led_lights.green.off()
            sleep(duration)
        self.led_lights.red.off()

    def blink_together(self, num, duration):
        for x in range(num):
            self.led_lights.on()
            sleep(duration)
            self.led_lights.off()
            sleep(duration)

    def alarm(self, num):
        for x in range(num):
            self.blink_alternating(2, 0.25)
            self.blink_together(2, 0.5)

if __name__=="__main__":

    tail_lights = TailLights()
    tail_lights.alarm(20)
  1. 将文件另存为TailLights.py
  2. 运行代码

您应该会看到一个 20 秒长、闪烁的 LED 显示屏。然而,在我们的代码中值得注意的是使用了 GPIO Zero 库中的LEDBoard类,如下所示:

led_lights = LEDBoard(red=21, green=20)

在这段代码中,我们从LEDBoard类实例化了一个名为led_lights的对象,并将其配置为redgreen的值,分别指向2120GPIO 引脚。通过使用LEDBoard,我们可以单独或作为一个单元控制 LED。blink_together方法将 LED 作为一个单元进行控制,如下所示:

def blink_together(self, num, duration):
        for x in range(num):
            self.led_lights.on()
            sleep(duration)
            self.led_lights.off()
            sleep(duration)

我们的代码相当不言自明;然而,我们应该指出的其他事情很少。当我们初始化TailLights对象时,我们会让 LED 灯短暂闪烁,表示该对象已初始化。这允许以后进行故障排除;尽管如此,如果我们觉得代码是多余的,那么我们可以稍后删除它:

def __init__(self):
        self.led_lights.on()
        sleep(0.25)
        self.led_lights.off()
        sleep(0.25)

当我们想要确保我们的 LED 没有断开连接时(毕竟,当尝试连接其他东西时,谁没有断开连接?),保持初始化代码在适当的位置可能会很方便。要从 shell 执行此操作,请键入以下代码:

import TailLights
tail_lights = TailLights.TailLights()

您应该会看到 LED 闪烁半秒钟。

现在我们已经测试了马达、伺服、相机和 LED,是时候将代码修改为类,以使其更加统一。在本节中,我们将让 T.A.R.A.S 跳舞。

让我们从封装移动机器人车车轮的代码开始:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码:
from gpiozero import Robot
from time import sleep

class RobotWheels:

    robot = Robot(left=(5, 6), right=(22, 27))

    def __init__(self):
        pass

    def move_forward(self):
        self.robot.forward(0.2)

    def move_backwards(self):
        self.robot.backward(0.2)

    def turn_right(self):
        self.robot.right(0.2)

    def turn_left(self):
        self.robot.left(0.2)

    def dance(self):
        self.move_forward()
        sleep(0.5)
        self.stop()
        self.move_backwards()
        sleep(0.5)
        self.stop()
        self.turn_right()
        sleep(0.5)
        self.stop()
        self.turn_left()
        sleep(0.5)
        self.stop()

    def stop(self):
        self.robot.stop()

if __name__=="__main__":

    robot_wheels = RobotWheels()
    robot_wheels.dance()
  1. 将文件另存为RobotWheels.py
  2. 运行代码

你应该看到 T.A.R.A.S.在你面前跳一支舞。确保连接到 T.A.R.A.S 的电线保持松动,以便 T.A.R.A.S 能够完成其任务。谁说机器人不会跳舞?

这段代码很容易解释。然而,值得注意的是,我们从dance方法中调用move_forwardmove_backwardsturn_leftturn_right函数的方式。我们实际上可以参数化移动之间的时间量,但这会使事情变得比需要的复杂一些。0.5秒的延迟(加上0.2的硬编码速度)似乎非常适合不会从桌子上掉下来的跳舞机器人。想象一下,T.A.R.A.在一个拥挤的舞池里,几乎没有活动的空间。

但是等等,还有更多。T.A.R.A.S 还可以移动头部、点亮并发出一些噪音。让我们开始添加这些动作。

由于 T.A.R.A.S 上的摄像头连接到头部,因此有必要使用摄像头功能封装头部运动(摄像头安装伺服系统):

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码:
from time import sleep
from time import ctime
from picamera import PiCamera
import Adafruit_PCA9685

class RobotCamera:

    pan_min = 150
    pan_centre = 375
    pan_max = 600
    tilt_min = 150
    tilt_max = 200
    camera = PiCamera()
    pwm = Adafruit_PCA9685.PCA9685()

    def __init__(self):
        self.tilt_up()

    def pan_right(self):
        self.pwm.set_pwm(0, 0, self.pan_min)
        sleep(2)

    def pan_left(self):
        self.pwm.set_pwm(0, 0, self.pan_max)
        sleep(2)

    def pan_mid(self):
        self.pwm.set_pwm(0, 0, self.pan_centre)
        sleep(2)

    def tilt_down(self):
        self.pwm.set_pwm(1, 0, self.tilt_max)
        sleep(2)

    def tilt_up(self):
        self.pwm.set_pwm(1, 0, self.tilt_min)
        sleep(2)

    def take_picture(self):
        sleep(2)
        self.camera.capture("/home/pi/image-" + ctime() + ".png")

    def dance(self):
        self.pan_right()
        self.tilt_down()
        self.tilt_up()
        self.pan_left()
        self.pan_mid()

    def secret_dance(self):
        self.pan_right()
        self.tilt_down()
        self.tilt_up()
        self.pan_left()
        self.pan_mid()
        self.take_picture()

if __name__=="__main__":

    robot_camera = RobotCamera()
    robot_camera.dance()
  1. 将文件另存为RobotCamera.py
  2. 运行代码

你应该看到 T.A.R.A.S.的头部向右移动,然后向下,然后向上,然后一直向左移动,然后返回中间并停止。

同样,我们尝试编写代码,以便于理解。在实例化RobotCamera对象时调用的init方法确保 T.a.R.a.S 在移动它之前头部朝上:

def __init__(self):
    self.tilt_up()

通过调用RobotCamera类,我们构造代码,以查看伺服和机器人车头部的运动,作为操作摄像头的一部分。虽然我们在示例中不使用相机,但我们很快就会使用它。最小和最大伺服位置的设定值通过以下试错确定:

pan_min = 150
pan_centre = 375
pan_max = 600
tilt_min = 150
tilt_max = 200

玩转这些价值观,使其适合你的 T.A.R.A.S 机器人车的建造。

dancesecret_dance方法使用机器人车头执行一系列动作来模拟舞蹈。它们基本上与secret_dance方法相同(除了末尾的take_picture调用),该方法使用 Raspberry Pi 摄像头拍摄照片并以基于日期的名称存储在主目录中。

现在,T.A.R.A.S 可以移动身体和头部,是时候制造一些噪音了:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下代码
from gpiozero import Buzzer
from time import sleep

class RobotBeep:

    buzzer = Buzzer(12)
    notes = [[0.5,0.5],[0.5,1],[0.2,0.5],[0.5,0.5],[0.5,1],[0.2,0.5]]

    def __init__(self, play_init=False):

        if play_init:
            self.buzzer.on()
            sleep(0.1)
            self.buzzer.off()
            sleep(1)

    def play_song(self):

        for note in self.notes:
            self.buzzer.on()
            sleep(note[0])
            self.buzzer.off()
            sleep(note[1])

if __name__=="__main__":

    robot_beep = RobotBeep(True)
  1. 将文件另存为RobotBeep.py
  2. 运行代码

您应该会听到 T.a.R.a.S.上激活的蜂鸣器发出一声短嘟嘟声。似乎有很多代码就是为了这样做的,不是吗?啊,但是等到下一节课,当我们充分利用RobotBeep课的时候。

RobotBeepinit函数允许我们打开和关闭类实例化后听到的初始蜂鸣声。这有助于测试蜂鸣器是否实际工作,在创建robot_beep对象时,我们通过将True传递给类来实现这一点:

robot_beep = RobotBeep(True)

notes列表和play_song方法执行类的实际魔法。该列表实际上是一个列表列表,因为每个值表示蜂鸣器播放或休息的时间:

for note in self.notes:
    self.buzzer.on()
    sleep(note[0])
    self.buzzer.off()
    sleep(note[1])

notes列表中循环,查看note变量。我们使用第一个元素作为保持蜂鸣器开启的时间长度,第二个元素作为重新开启蜂鸣器之前的休息时间。换句话说,第一个元素决定音符的长度,第二个元素决定该音符和下一个音符之间的间距。notes列表和play_song方法赋予 T.A.R.A.S 唱歌的能力(尽管没有旋律)。

我们将在下一节中使用play_song方法。

这是一个寒冷、黑暗、沉闷的十二月夜晚。关于我们的对手我们所知不多,但我们知道他们喜欢跳舞。T.A.R.A.S.被分配到一个位于敌方领土深处的当地舞蹈俱乐部。今晚所有感兴趣的人都在那里。如果你选择接受,你的任务是写一个程序让 T.a.R.a.S 在俱乐部拍秘密照片。但是,它不能看起来像是 T.A.R.A.S 在拍照。T.A.R.A.S 必须跳舞!如果我们的对手发现 T.A.R.A.S 在拍照,那就太糟糕了。真糟糕!想想帝国反击战中的 C3PO 吧。

因此,我们有能力让 T.A.R.A.S.移动它的头部和身体,发出声音,点亮,并拍照。让我们把所有这些放在一起,以便完成任务:

  1. 从应用程序菜单|编程| Thonny Python IDE 打开 Thonny
  2. 单击新建图标创建一个新文件
  3. 在文件中键入以下内容:
from RobotWheels import RobotWheels
from RobotBeep import RobotBeep
from TailLights import TailLights
from RobotCamera import RobotCamera

class RobotDance:

    light_show = [2,1,4,5,3,1]

    def __init__(self):

        self.robot_wheels = RobotWheels()
        self.robot_beep = RobotBeep()
        self.tail_lights = TailLights()
        self.robot_camera = RobotCamera()

    def lets_dance_incognito(self):
        for tail_light_repetition in self.light_show:
            self.robot_wheels.dance()
            self.robot_beep.play_song()
            self.tail_lights.alarm(tail_light_repetition)
            self.robot_camera.secret_dance()

if __name__=="__main__":

    robot_dance = RobotDance()
    robot_dance.lets_dance_incognito()
  1. 将文件另存为RobotDance.py
  2. 运行代码

在秘密拍照之前,你应该看到 T.A.R.A.S.表演了一系列动作。如果你在舞蹈结束后检查 Raspberry Pihome文件夹,你会看到六张新照片。

代码中值得注意的是使用了名为light_show的列表。我们以两种方式使用此列表。首先,存储在列表中的值被传递给我们在RobotDance类中实例化的TailLights对象的alarm方法。我们使用lets_dance_incognito方法中的tail_light_repetition变量进行此操作,如下所示:

def lets_dance_incognito(self):
    for tail_light_repetition in self.light_show:
        self.robot_wheels.dance()
        self.robot_beep.play_song()
        self.tail_lights.alarm(tail_light_repetition)
        self.robot_camera.secret_dance()

正如您在前面的代码中所看到的,alarm方法的TailLights类的变量名为tail_lights。这将导致 LED 根据tail_light_repetition的值多次按顺序显示。例如,当2的值被传递到alarm方法(在light_show列表中的第一个值)时,LED 序列将被执行两次。

我们运行了六次lets_dance_incognito方法。这是基于light_show列表中的值的数量。这是我们使用light_show的第二种方式。为了增加或减少 T.A.R.A.S 表演舞蹈的次数,我们可以从light_show列表中增加或减少一些数字。

当我们在名为robot_cameraRobotCamera对象上调用secret_dance方法时,对于light_show列表中的每个值(在本例中为六个),在舞蹈结束后,我们的主目录中应该有六张带有日期名称的照片。

T.A.R.A.S 表演完舞蹈后,检查主目录中 T.A.R.A.S 在舞蹈中拍摄的照片。任务完成了!

到本章结束时,您应该已经熟悉了用 Python 代码控制以 Raspberry Pi 为动力的机器人。我们一开始只是使用简单的代码让机器人车上的各种组件工作。在我们对机器人车确实使用 Python 命令移动感到满意之后,我们将代码封装在类中以使其更易于使用。这就产生了RobotDance类,其中包含对类的调用,而这些类又封装了我们机器人的控制代码。这使我们能够将RobotDance类用作黑匣子,抽象出控制代码,并使我们能够专注于为 T.a.R.a.S.设计舞步的任务。

第 15 章将机器人车的感官输入连接到网络中,我们将从 T.A.R.A.S(距离传感器值)中提取感官信息并发布到网络上,然后从桌面上的电线释放 T.A.R.A.S 并将其释放。

  1. 对还是错?LEDBoard对象允许我们同时控制多个 LED。
  2. 对还是错?RobotCamera对象上的注释列表用于移动摄像机支架。
  3. 对还是错?我们虚构故事中的对手喜欢跳舞。
  4. dancesecret_dance方法有什么区别?
  5. 机器人图书馆的名称是什么?
  6. 受旧警笛的启发,揭露犯罪行为的术语是什么?
  7. 对还是错?封装控制代码是一个毫无意义和不必要的步骤。
  8. TailLights课程的目的是什么?
  9. 我们将使用哪种类别和方法将机器人车转向右侧?
  10. RobotCamera课程的目的是什么?

学习 GPIO Zero 的最佳参考书之一是 GPIO Zero PDF 文档本身。谷歌搜索 GPIO Zero PDF,然后下载并阅读。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

AI技术内参 -〔洪亮劼〕

React实战进阶45讲 -〔王沛〕

算法面试通关40讲 -〔覃超〕

Vue开发实战 -〔唐金州〕

数据中台实战课 -〔郭忆〕

软件设计之美 -〔郑晔〕

Tony Bai · Go语言第一课 -〔Tony Bai〕

运维监控系统实战笔记 -〔秦晓辉〕

AI大模型之美 -〔徐文浩〕