一起打造自己的自动驾驶小车mycar - 3.手机控制小车移动

Car Assembled

小车组装好后,我们开始编写在小车上运行的python程序!

本次我们实现用手机来控制小车的移动。效果如下:


Remote Control on Phone

1. 功能

控制小车移动是通过一个网页实现的,这样通过手机或者电脑上的浏览器就能控制小车的移动。

网页上部是监控区域,可以看到小车摄像头的实时画面。

网页中部是控制区域,有两个摇杆,左摇杆控制小车左右转向,右摇杆控制小车前进和后退。

网页下部是功能选择,用于后面扩展功能,Autonomous Mode用于在手动驾驶和自动驾驶模式间切换,Record Video用于录制小车行进的视频,后面可以使用视频和控制输入来训练自动驾驶模型。

2. 代码概述

本次我们实现的功能虽然看起来不太复杂,但部分基础代码对以后的扩展是非常重要的,因此有必要设计一个良好的架构。

2.1 消息总线

在真实的汽车上有一个通信总线 - CAN,Controller Area Network,控制器局域网。汽车上需要通信、交换数据的部件都连接到该总线上。它可以实现点对点、一对多、广播的通信方式。CAN有行业的国际标准。

在我们的项目里用消息总线(message bus)模拟CAN,各组件通过发布/订阅(publish/subscribe)的方式进行通信。redis、kafka、rabbitmq、zeromq等都能实现所需功能,其中redis和zeromq比较轻量,在对比了redis和zeromq的性能后,我选择了zeromq作为mycar的消息中间件。

2.2 组件

组件是指小车上实现特定功能,可以独立运行的部件。mycar项目的组件可能有:运动控制、摄像头、蓝牙手柄、激光雷达、IMU等。

在这篇文章里,我们需要实现的功能模块是:

  • 运动控制,模块名:actuator
  • 摄像头,模块名:camera
  • 网页控制,模块名:web_controller

各具体组件的UML类图如下:


Class Diagram
  • Car
    代表一辆小车,是项目的主类,负责加载配置文件和启动各组件(Component)。
    创建该类需要传入配置文件和小车的运行时间。

  • Component
    是小车组件的抽象接口,组件主要的行为方法是runon_messagepublish_messagestartshutdown方法在小车启动和停止时会自动调用。on_messagepublish_message用来收发消息。run方法在start返回True时有作用,用于在单独线程执行组件长时间运行的业务逻辑,例子是摄像头不断产生图像数据。

  • CAN和ZmqCAN
    代表消息总线的抽象类和zeromq实现,它也是一种组件Component。主要方法是向指定的channel发送消息和订阅消息,分别是publish(channel, message)subscribe(channels, listener)

  • PWMSteering和PWMThrottle
    actuator模块,分别是控制小车方向和速度的类。依赖PCA9685类来发出PWM信号。

  • Camera
    是小车的摄像头类,用于产生实时图像。

  • WebController
    WebController组件用于接收页面输出,发送控制消息到消息总线。

2.3 mycar代码目录结构

├── bin
│   └── run.sh
├── config
│   └── web_drive.yml
├── README.md
├── requirements.txt
└── src
    ├── car.py
    ├── components
    │   ├── __init__.py
    │   ├── actuator.py
    │   ├── camera.py
    │   ├── can.py
    │   ├── component.py
    │   ├── templates
    │   │   └── index.html
    │   ├── web_controller.py
    │   └── zmq_can.py
    └── utils
        ├── __init__.py
        ├── map_range.py
        └── pca9685.py

下面分章节详细介绍一些重要的类和组件。

3. 运动控制

3.1 I2C、PWM与PCA9685

PWM,是指脉宽调制Pulse Width Modulation,遥控车一般使用PWM来控制转向和速度,所以2通道的遥控器一路输出到转向舵机,一路输出到电子调速器ESC。

I2C,IC间的通信协议,Inter-Integrated Circuit,包含一条双向串行数据线SDA,一条串行时钟线SCL。

PCA9685,是一个通用的16路舵机控制板,有16个输出端口,我们需要用到其中2个。

我们不需要了解如何产生I2C和PWM信号,在我们的项目里,由软件控制jetson nano的GPIO口产生I2C信号给PCA9685,PCA9685再生成PWM信号去驱动小车运动。

接线方式上一篇文章有讲到。

3.2 PCA9685类

这个类使用了adafruit公司的python包adafruit-circuitpython-servokit来控制舵机角度与ESC速度。它已经封装好了ServoKit这个类。

使用上一讲的接线方式,ServoKit的使用方法如下:

servokit = ServoKit(channels=16, address=0x40)
# 控制PCA9685第一个口的舵机的转动角度
servokit.servo[0].angle = 135  # 角度范围 0 ~ 180度
# 控制PCA9685第二个口的ESC的速度
servokit.continuous_servo[1].throttle = 0.3  # 速度的范围是 -1 ~ 1,大于0表示向前移动,如果输入负数代表后退,注意从前进切换到后退要输入两次同样的负数

3.3 PWMSteering和PWMThrottle类

这两个类实现了Component接口,调用PCA9685类来实现功能。

  • PWMSteering
    支持定义正前方的前进角度(straight_angle),以防小车有偏差;也支持自定义可转的角度范围(full_left_angle, full_right_angle)。默认的是小车舵机支持的90度的转向范围。

  • PWMThrottle
    支持自定义控制速度的范围(min_throttle, max_throttle),如(-0.3, 0.3),会把(-1, 1)的值映射到这个范围内,因为小车速度较快,调小点可以更方便控制小车,以防撞坏。

4. 摄像头

Camera这个类比较简单,它使用cv2.VideoCapture()来捕捉摄像头图像。

如果连接的是CSI摄像头,那么使用gstreamer pipline + Nvidia的nvarguscamerasrc,参数可以参考官方文档。

如果是在其他平台上做测试,可以使用cv2.VideoCapture(0)来使用默认的摄像头设备。

5. 网页控制

WebController类用Flask来启动了一个本地web server,端口为8080

实时图像展示部分使用的是返回帧图片的HTTP报文的方式,使用HTML标签就可以让浏览器会不断加载新的帧。帧的格式为:

--frame
Content-Type: image/jpeg

[图像字符串]

这种方式实现比较简单。

摇杆部分使用的是一个开源的js库做的,其实用一个摇杆就可以实现方向和速度的调节的,这里我选择分开来控制。

两个功能控制选项目前还没有用到,先忽略。

6. 配置文件

最后是小车运行的配置文件,用来配置小车需要加载的所有组件Component,一个例子如下:

components:
  zmq_can:  # 模块名
    server_mode: True  # 创建实例的参数

  actuator:  # 该模块有两个类
    PWMSteering:  # 第一个类
      subscription: ['web_steering']  # 接收来自这个channel的消息
      channel: 0

    PWMThrottle:  # 第二个类
      subscription: ['web_throttle']
      channel: 1
      min_throttle: -0.3
      max_throttle: 0.3

  camera:  #该模块只有一个类
    publication: ['cam/image']  # 把图像发送到这个channel
    device: '/device/video0'

  web_controller:
    subscription: ['cam/image']
    publication: ['web_steering', 'web_throttle', 'web_record', 'web_autonomous']  # 发送消息到多个channel
  • 顶层,使用components作为键,代表是小车的组件
  • 次层缩进,以组件的模块(文件)作为键,如果该模块只有一个类,那么第三层缩进可以直接写该类的创建时传入的参数,如camera
  • 最后层缩进,配置组件类创建时传入的参数

6.1 subscription和publication属性

这两个属性是每个Component都有的属性,默认为空([]),分别代表组件要订阅的消息和要发出的消息。

7. 安装与运行

介绍完项目的代码后,我们看看怎么跑起来。

7.1 clone代码

代码已开源到github,首先clone一下:

git clone https://github.com/evan-wu/mycar.git --branch blog-3 --single-branch

7.2 安装依赖

注意jetson nano的Jet Pack已经自带了python3和编译好的opencv,切勿自己手动安装opencv。安装以下4个依赖就可以了:

pip3 instal pyyaml adafruit-circuitpython-servokit flask pyzmq

7.3 运行

cd mycar
chmod +x bin/run.sh
bin/run.sh config/web_drive.yml 120

用浏览器(手机浏览器)访问: http://:8080 就可以了。

欢迎github/blog点赞、留言、讨论!

后续预告:手柄控制小车移动,实现PID寻线小车算法...

你可能感兴趣的:(一起打造自己的自动驾驶小车mycar - 3.手机控制小车移动)