小车组装好后,我们开始编写在小车上运行的python程序!
本次我们实现用手机来控制小车的移动。效果如下:
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类图如下:
Car
代表一辆小车,是项目的主类,负责加载配置文件和启动各组件(Component)。
创建该类需要传入配置文件和小车的运行时间。Component
是小车组件的抽象接口,组件主要的行为方法是run
、on_message
和publish_message
。start
和shutdown
方法在小车启动和停止时会自动调用。on_message
和publish_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://
就可以了。
欢迎github/blog点赞、留言、讨论!
后续预告:手柄控制小车移动,实现PID寻线小车算法...