前言
立下的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 協議》,轉載必須註明作者和本文連結