树莓派 - 实战篇 [基于 websocket 实现手机远程控制树莓派小车]
前言
立下的flag总是要还的,这篇的文章可能比较长,基于websocket来远程控制树莓派小车。首先需要有一个公网服务器,我这边已经在公网启用了一个websocket服务,作为远程控制树莓派小车数据中转平台。
设计思路
1.当树莓派启动后,需要连接公网websocket服务,绑定设备ID和修改树莓派的在线状态
2.H5页面,页面根据设备ID查询设备的在线状态,在线则允许操作
3.H5页面websocket客户端处理客户端的前进、后退、停止、左转和右转操作,与树莓派数据互通达到控制树莓派的目的
树莓派小车组装
这个不多说,某宝上面搜索即可,买个套装带常用传感器的就可以,教程掌柜也会提供。我的教程会针对四轮驱动来讲,这里就跳过了。
Web端
web端操作很简单5个按钮,分别控制树莓派小车的前后左右停操作,直接上代码
<html>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<style type="text/css">
#car {
position: fixed;
width: 300px;
bottom: 20px;
margin: 20px auto;
left: 0;
right: 0;
}
button.top {
display: block;
height: 50px;
width: 90px;
margin: 10px auto;
}
button.left {
height: 50px;
width: 90px;
margin-right: 10px;
}
button.right {
height: 50px;
width: 90px;
}
button.bottom {
display: block;
height: 50px;
width: 90px;
margin: 10px auto;
}
button.stop {
height: 50px;
width: 90px;
margin-right: 10px;
}
</style>
<head>
<title>Smarty Car</title>
</head>
<body>
<p id="message"></p>
<div id="car">
<button class="top" onclick="send('forward')">Forward</button>
<button class="left" onclick="send('left')">Left</button>
<button class="stop" onclick="send('stop')">Stop</button>
<button class="right" onclick="send('right')">Right</button>
<button class="bottom" onclick="send('back')">Back</button>
</div>
</body>
<script>
// 初始化websocket客户端
var socket, fd;
var host = "ws://192.168.33.10:9511/";
socket = new WebSocket(host);
// 连接完成 询问树莓派是否在线
socket.onopen = function () {
var data = new Object();
data.devices = 'smart_car';
data.action = 'init';
socket.send(JSON.stringify(data));
};
// 接收消息
socket.onmessage = function (evt) {
var jsonData = JSON.parse(evt.data);
// 如果返回树莓派在线状态
if (jsonData.err_code != 0) {
fd = 0
document.getElementById('message').textContent = jsonData.err_msg;
} else {
//返回在线 绑定树莓派的websocket fd
fd = jsonData.fd;
document.getElementById('message').textContent = '终端在线';
}
};
// 连接关闭事件
socket.onclose = function () {
}
//发送消息
function send(msg) {
if (!fd) {
document.getElementById('message').textContent = '终端未在线!';
return false;
}
var data = new Object();
data.devices = fd;
switch (msg) {
case 'forward':
case 'left':
case 'stop':
case 'right':
case 'back':
data.action = msg;
break;
default:
document.getElementById('message').textContent = '错误函数调用';
return false;
}
try {
socket.send(JSON.stringify(data));
} catch (ex) {
}
}
</script>
</html>
websocket服务端
我这边使用Laravel框架搭建web服务及websocket服务器,安装了swoole扩展,如果是php专业且用laravel框架搭建websocket的服务的,可以安装这个包来实现简单的websocket服务。也欢迎各位star和issues。如果不想搭建或没有公网IP可以私信我开放接口给各位。
composer require wenjunting50779/laravel-fast
树莓派端
实现也简单,不足200行代码,起了两个线程。一个线程监听来自 Websocket 服务器的事件,一个线程使用超声波模块测量前面障碍物的距离,话不多说上代码。
import RPi.GPIO as GPIO
import websocket
import json
import threading
import time
'''
开机运行当前脚本
1.告知 WebSocket 服务器,我已准备就绪
2.监听来自服务器的消息
3.检测前方障碍物的距离
'''
# 共享全局变量 供多个线程使用,确定当前小车状态
action = 'stop'
def socket_client():
# 创建 WebSocket 客户端连接服务器,并发送消息告知服务器准备就绪
ws = websocket.create_connection("ws://192.168.33.10:9511")
req = {"terminal": "smart_car"}
ws.send(json.dumps(req))
# 监听来自服务器的消息
while True:
res = ws.recv()
res = json.loads(res)
if res.__contains__('action') and res.__contains__('err_code') and res['err_code'] == 0:
global action
action = res['action']
# 执行后退操作 设置各引脚的高低电位,设置占空比 30/100 控制速度
if res['action'] == 'back':
GPIO.output(35, GPIO.LOW)
GPIO.output(37, GPIO.HIGH)
GPIO.output(13, GPIO.LOW)
GPIO.output(15, GPIO.HIGH)
GPIO.output(12, GPIO.LOW)
GPIO.output(16, GPIO.HIGH)
GPIO.output(18, GPIO.LOW)
GPIO.output(22, GPIO.HIGH)
speed(30)
# 执行前进操作 设置各引脚的高低电位,与后退相反,设置占空比 30/100 控制速度
elif res['action'] == 'forward':
GPIO.output(37, GPIO.LOW)
GPIO.output(35, GPIO.HIGH)
GPIO.output(15, GPIO.LOW)
GPIO.output(13, GPIO.HIGH)
GPIO.output(16, GPIO.LOW)
GPIO.output(12, GPIO.HIGH)
GPIO.output(22, GPIO.LOW)
GPIO.output(18, GPIO.HIGH)
speed(30)
# 执行停止操作,设置占空比 0/100 控制速度
elif res['action'] == 'stop':
speed(0)
# 执行右转操作 设置各引脚的高低电位,设置占空比 40/100 控制速度
elif res['action'] == 'right':
GPIO.output(35, GPIO.LOW)
GPIO.output(37, GPIO.HIGH)
GPIO.output(13, GPIO.LOW)
GPIO.output(15, GPIO.HIGH)
GPIO.output(12, GPIO.LOW)
GPIO.output(16, GPIO.LOW)
GPIO.output(18, GPIO.HIGH)
GPIO.output(22, GPIO.LOW)
speed(40)
# 执行左转操作 设置各引脚的高低电位,设置占空比 40/100 控制速度
elif res['action'] == 'left':
GPIO.output(35, GPIO.HIGH)
GPIO.output(37, GPIO.LOW)
GPIO.output(13, GPIO.LOW)
GPIO.output(15, GPIO.LOW)
GPIO.output(12, GPIO.LOW)
GPIO.output(16, GPIO.HIGH)
GPIO.output(18, GPIO.LOW)
GPIO.output(22, GPIO.HIGH)
speed(40)
else:
print('error')
def check():
'''
使用超声波测距模块检测前方障碍物距离控制当前小车速度
:return:
'''
while True:
global action
distance = Measure()
# 如果距离小于10cm且当前状态为前进时执行停止操作
if (distance < 10) and action == 'forward':
speed(0)
# 如果距离基于10~30cm之间且当前状态为前进时控制占空比 20/100 减速
elif action == 'forward' and 10 < distance < 30:
speed(20)
else:
pass
# 多线程执行 线程启动时连接服务器
class EventThread(threading.Thread):
def run(self):
socket_client()
# 多线程执行 线程启动时检测前方障碍物距离
class CheckThread(threading.Thread):
def run(self):
check()
# 初始化树莓派引脚
def init():
GPIO.setmode(GPIO.BOARD)
GPIO.setup(35, GPIO.OUT)
GPIO.setup(37, GPIO.OUT)
GPIO.setup(13, GPIO.OUT)
GPIO.setup(15, GPIO.OUT)
GPIO.setup(12, GPIO.OUT)
GPIO.setup(16, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.setup(11, GPIO.OUT)
# 设置超声模块输入输出引脚
GPIO.setup(31, GPIO.OUT)
GPIO.setup(33, GPIO.IN)
# 控制超声波测距模块输出信号,获得输入信号,完成测距
def Measure():
# send
GPIO.output(31, True)
time.sleep(0.00001) # 1us
GPIO.output(31, False)
# start recording
while GPIO.input(33) == 0:
pass
start = time.time()
# end recording
while GPIO.input(33) == 1:
pass
end = time.time()
# compute distance
distance = round((end - start) * 343 / 2 * 100)
return distance
# 调整小车速度
def speed(s):
p.ChangeDutyCycle(s)
# 初始化引脚
init()
# 初始小车速度
p = GPIO.PWM(11, 100)
p.start(0)
# 启动线程监听服务端消息
eventThread = EventThread()
eventThread.start()
# 启动线程检测前方障碍物距离
checkThread = CheckThread()
checkThread.start()
思考
俗话说:授人以鱼不如授人以渔。这些代码和思路只是提供一些参考,还是有一些问题没有考虑,比如:电机是怎么控制正反转的,如何控制速度的? 一些电子元器件的控制将在树莓派-传感器篇中讲解。源码也会在github上供大家参考。欢迎交流讨论。
本作品采用《CC 协议》,转载必须注明作者和本文链接
本来想白漂你的前端呢,结果咋比我的还丑啊 :joy:。
@tiansai 白嫖是不可能的,想想就好了
大佬实现了吗 :see_no_evil:
@zmxyzmxy1234 最近比较忙还没时间更新 :sweat_smile:
生产队的驴都不敢这么消极怠工,赶紧写后续的!
@191834552 上面的代码已经可以控制小车了,你想了解啥
请问为什么import websocket 会提示ModuleNotFoundError: No module named 'websocket'呢