树莓派智能车AlphaBot教程9:python-bottle
上两章我们介绍了用webiopi实现网页控制,对网页控制也有了一定的了解。这一章我们介绍通过Python bottle实现网页控制。Bottle是一个非常小巧的微型Python web 框架。
Bottle
是一个非常小巧但高效的微型
Python
Web 框架,它被设计为仅仅只有一个文件的Python模块,并且除Python标准库外,它不依赖于任何第三方模块。* 路由(Routing):将请求映射到函数,可以创建十分优雅的 URL* 模板(Templates):Pythonic 并且快速的 Python 内置模板引擎,同时还支持 mako, jinja2, cheetah 等第三方模板引擎* 工具集(Utilites):快速的读取 form 数据,上传文件,访问 cookies,headers 或者其它 HTTP 相关的 metadata* 服务器(Server):内置HTTP开发服务器,并且支持 paste, fapws3, bjoern, Google App Engine, Cherrypy 或者其它任何 WSGI HTTP 服务器
安装 Bottle
sudo apt-get install python-bottle
一个Hello World 程序
新建一个HelloWorld.py文件,并输入如下代码保存。
#!/usr/bin/python
# -*- conding:utf-8 -*-
from bottle import *
@route('/helloworld/:yourwords')
def hello(yourwords):
return 'hello world. ' + yourwords
run(host='0.0.0.0', port=8080)
运行程序:
sudo python HelloWorld.py
在浏览器中输入:http://192.168.6.115:8080/helloworld/Bottle (IP地址改为树莓派实际地址)
就会显示如下页面。(改变helloworld后面的字符串,显示也不会不一样)
程序中用到两个Bottle组件,route()和run()函数。 route() 可以将一个函数与一个URL进行绑定,在上面的示例中,route 将 “/hello/:yourwords’ 这个URL地址绑定到了 hello(yourwords) 这个函数上. 我们获得请求后,hello() 函数返回简单的字符串. 最后,run() 函数启动服务器,并且我们设置它在 “localhost” 和 8080 端口上运行
通过Web控制RGB LED。
上面只是小试牛刀,下面再来一个酷炫的。通过网页控制RGB 彩灯,下面以RGB LED HAL模块的实力程序为例。
本程序一共包含四个文件color_picker.png color_range.png index.html main.py。其中前面两个文件为图片,index.html为HTML网页文件,main.py为脚本程序。
=== main.py: ===
#!/usr/bin/python
from bottle import get,request, route, run, static_file,template
import time, threading
from neopixel import *
# LED strip configuration:
LED_COUNT = 4 # Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 5 # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
# Create NeoPixel object with appropriate configuration.
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# Intialize the library (must be called once before other functions).
strip.begin()
strip.show()
rgb = 0
light_type = 'static' #'static' 'breath' 'flash'
#Access the file root directory
@get("/")
def index():
global rgb, light_type
rgb = 0xffffff
light_type = 'static'
return static_file('index.html', './')
#Static files on the page need to be processed
@route('/<filename>')
def server_static(filename):
return static_file(filename, root='./')
#get the rgb value by POST
@route('/rgb', method='POST')
def rgbLight():
red = request.POST.get('red')
green = request.POST.get('green')
blue = request.POST.get('blue')
#print('red='+ red +', green='+ green +', blue='+ blue)
red = int(red)
green = int(green)
blue = int(blue)
if 0 <= red <= 255 and 0 <= green <= 255 and 0 <= blue <= 255:
global rgb
rgb = (red<<8) | (green<<16) | blue
#get the type by POST
@route('/lightType', method='POST')
def lightType():
global light_type
light_type = request.POST.get('type')
print("lightType="+light_type)
#Light cycle detection control
def lightLoop():
global rgb, light_type
flashTime = [0.3, 0.2, 0.1, 0.05, 0.05, 0.1, 0.2, 0.5, 0.2] #Blink time
flashTimeIndex = 0 #flash time index
f = lambda x: (-1/10000.0)*x*x + (1/50.0)*x #Simulate the breathing light with parabola
x = 0
while True:
if light_type == 'static':
for i in range(0,strip.numPixels()):
strip.setPixelColor(i, rgb)
strip.show()
time.sleep(0.05)
elif light_type == 'breath':
red = int(((rgb & 0x00ff00)>>8) * f(x))
green = int(((rgb & 0xff0000) >> 16) * f(x))
blue = int((rgb & 0x0000ff) * f(x))
_rgb = int((red << 8) | (green << 16) | blue)
for i in range(0,strip.numPixels()):
strip.setPixelColor(i, _rgb)
strip.show()
time.sleep(0.02)
x += 1
if x >= 200:
x = 0
elif light_type == 'flash':
for i in range(0,strip.numPixels()):
strip.setPixelColor(i, rgb)
strip.show()
time.sleep(flashTime[flashTimeIndex])
for i in range(0,strip.numPixels()):
strip.setPixelColor(i, 0)
strip.show()
time.sleep(flashTime[flashTimeIndex])
flashTimeIndex += 1
if flashTimeIndex >= len(flashTime):
flashTimeIndex = 0
#Open a new thread for rgb light display
t = threading.Thread(target = lightLoop)
t.setDaemon(True)
t.start()
#Set the server ip address and port (hint:you set your raspberry ip address before use )
run(host="0.0.0.0", port=8000)
这里采用的是W2812B灯珠,关于这个控制在这里就不在讲了。
@get("/"), 这作用是创建一个网页静态文件传输通道。流浪器访问时会打开目录下的index,html文件, @route('/<filename>') 这个是用来传输静态文件,两张图片的。 @route('/rgb', method='POST'),@route('/lightType', method='POST')是创建两个URL,分别用来传输RGB的值,和灯控制类型的。 获取到值是分别储存在reg,green,blue和light_type 中。 threading.Thread另外创建一个python线程,执行lightLoop()函数,实时处RGB LED的状态。RGB LED有静态显示,闪烁显示已经呼吸灯显示三种显示方式。
=== index.html代码: ===
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<!--Adapt to mobile phone size, not allowed to zoom-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>web rgb</title>
<script src="http://code.jquery.com/jquery.js"></script>
<style type="text/css">
body,div,img{ border:0; margin:0; padding:0;}
</style>
</head>
<body>
<div style="width:100%; height:40px; line-height:40px; text-align:center; font-size:20px; color:white; background-color:blue; margin:auto">
Controlling RGB LED with the web
</div>
<img width="300" height="300" src="color_range.png" id="myimg" style="display:none" alt="range"/>
<div style="width:300px; height:300px; position:relative; text-align:center; margin:auto; margin-top:20px; margin-bottom:40px;" id="colorRange">
<canvas id="mycanvas" width="300" height="300">
Your browser does not support the html5 Canvas element
</canvas>
<img width="30" height="30" src="color_picker.png" id="picker" style="position:absolute; top:135px; left:135px;" alt="picker" />
</div>
<div style="font-size:20px;align:center;text-align:center;margin:auto; border:1px solid gray; border-radius:10px; width:320px; height:40px; line-height:40px;">
<div>
<input type="radio" name="radio1" value="static" checked/>static
<input type="radio" name="radio1" value="breath"/>breath
<input type="radio" name="radio1" value="flash"/>flash
</div>
</div>
</body>
<script>
var RadiusRange = 150;
var RadiusPicker = 15;
var offsetX = window.screen.width / 2 - RadiusRange;
var offsetY = 60;
var centerX = offsetX + RadiusRange;
var centerY = offsetY + RadiusRange;
var colorRange = $('#colorRange')[0];
var colorPicker = $('#picker')[0];
var myCanvas = $('#mycanvas')[0];
var myImg = $('#myimg')[0];
var ctx = myCanvas.getContext('2d');
myImg.onload = function(){ctx.drawImage(myImg, 0, 0);}
colorRange.addEventListener('touchstart', touch, false);
colorRange.addEventListener('touchmove', touch, false);
function touch(e)
{
var X = e.touches[0].clientX;
var Y = e.touches[0].clientY;
var x = X - centerX;
var y = Y - centerY;
if(Math.sqrt(x*x + y*y) < RadiusRange-5)
{
colorPicker.style.left = X - offsetX - RadiusPicker +'px';
colorPicker.style.top = Y - offsetY - RadiusPicker +'px';
var rgba = ctx.getImageData(X-offsetX, Y-offsetY, 1, 1).data;
var red = rgba['0'];
var green = rgba['1'];
var blue = rgba['2'];
$.post('/rgb', {red: red, green: green, blue: blue});
}
event.preventDefault();
}
$('input').click(function() {
var type = this.value;
$.post('/lightType', {type: type});;
});
</script>
</html>
分析:
【1】 index.html文件包含<head>,<body>,<script>三部分 <head>部分中<script src="http://code.jquery.com/jquery.js"></script> 这个语句是通过网页链接引入jquery.js库文件,这个文件和上一张webiopi中的那个文件是一样的。
【2】
<body>部分是设置网页界面,包含一个网页标题,彩色图片,以及三个LED 显示类型选择按键。
【3】
<sritpt>为脚本,脚本中对touchstart事件和touchmove事件监听。这两个事件只在手机端起作用,所以在pc端访问时拖动鼠标,是不能选中颜色的。pc端相对应的事件为:onmousedown、onmousemove。
当事件触发时会调用touch()函数,获取当前的颜色并POST方式发送到/rgb。服务器端接受到传过来的数据就会触发main.py中的rgbLight()函数。
$('input').click(function()
这里是注册按键点击事件。但选择按键被按下时会触发函数,将当前的按键ID通过POST方式发送到/lightType 。从而会触发main.py中的lightType()函数。
通过web控制AlphaBot2智能车 下载AlphaBot的程序,程序中web_Control目录即通过Bottle控制小车的。 工程目录下包含AlphaBot2.py,PCA9685.py,index.html,main.py四个文件。其中AlphaBot2.py为小车控制库文件,PCA9685.py这个是舵机控制库文件。主要看index.html和main.py这两个文件。
=== main.py代码: ===
#!/usr/bin/python
# -*- coding:utf-8 -*-
from bottle import get,post,run,request,template
from AlphaBot import AlphaBot
from PCA9685 import PCA9685
import threading
Ab = AlphaBot()
pwm = PCA9685(0x40)
pwm.setPWMFreq(50)
#Set the Horizontal servo parameters
HPulse = 1500 #Sets the initial Pulse
HStep = 0 #Sets the initial step length
pwm.setServoPulse(0,HPulse)
#Set the vertical servo parameters
VPulse = 1500 #Sets the initial Pulse
VStep = 0 #Sets the initial step length
pwm.setServoPulse(1,VPulse)
@get("/")
def index():
return template("index")
@post("/cmd")
def cmd():
global HStep,VStep
code = request.body.read().decode()
print(code)
if code == "stop":
HStep = 0
VStep = 0
Ab.stop()
elif code == "forward":
Ab.forward()
elif code == "backward":
Ab.backward()
elif code == "turnleft":
Ab.left()
elif code == "turnright":
Ab.right()
elif code == "up":
VStep = -5
elif code == "down":
VStep = 5
elif code == "left":
HStep = 5
elif code == "right":
HStep = -5
return "OK"
def timerfunc():
global HPulse,VPulse,HStep,VStep,pwm
if(HStep != 0):
HPulse += HStep
if(HPulse >= 2500):
HPulse = 2500
if(HPulse <= 500):
HPulse = 500
#set channel 2, the Horizontal servo
pwm.setServoPulse(0,HPulse)
if(VStep != 0):
VPulse += VStep
if(VPulse >= 2500):
VPulse = 2500
if(VPulse <= 500):
VPulse = 500
#set channel 3, the vertical servo
pwm.setServoPulse(1,VPulse)
global t #Notice: use global variable!
t = threading.Timer(0.02, timerfunc)
t.start()
t = threading.Timer(0.02, timerfunc)
t.setDaemon(True)
t.start()
run(host="0.0.0.0",port="8000")
程序分析;
@get("/") 创建一个创建一个网页文件传输通道,传输index.html文件。 @post("/cmd") 创建一下URL,对接受到的命令做出个中反应。其中forward,backward,turnleft,turnright,stop分别控制小车前进,后退,左转,右转,停止。up,down,left,right,控制舵机上下左右移动。 timerfunc()函数用来处理舵机转动的定时函数。 最后调用run() 函数启动服务器,并且我们设置它在 “localhost” 和 8080 端口上运行。
=== index.hmtl代码: ===
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AlphaBot</title>
<link href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" media="screen">
<script src="http://code.jquery.com/jquery.js"></script>
<script>
$(function(){
var isTouchDevice = "ontouchstart" in document.documentElement ? true : false;
var BUTTON_DOWN = isTouchDevice ? "touchstart" : "mousedown";
var BUTTON_UP = isTouchDevice ? "touchend" : "mouseup";
$("button").bind(BUTTON_DOWN,function(){
$.post("/cmd",this.id,function(data,status){
});
});
$("button").bind(BUTTON_UP,function(){
$.post("/cmd","stop",function(data,status){
});
});
});
</script>
<style type="text/css">
button {
margin: 10px 15px 10px 15px;
width: 50px;
height: 50px;
}
input {
margin: 10px 15px 10px 15px;
width: 50px;
height: 50px;
}
</style>
</head>
<body>
<div id="container" class="container" align="center">
<img width="320" height="240" src="http://192.168.10.130:8080/?action=stream"><br/>
<table align="center">
<tr>
<td align="center"><b>Motor Contrl</b></td>
<td align="center"><b>Servo Contrl</b></td>
</tr>
<tr>
<td>
<div align="center">
<button id="forward" class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-up"></button>
</div>
<div align="center">
<button id='turnleft' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-left"></button>
<button id='turnright' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-right"></button>
</div>
<div align="center">
<button id='backward' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-down"></button>
</div>
</td>
<td>
<div align="center">
<button id="up" class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-up"></button>
</div>
<div align="center">
<button id='left' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-left"></button>
<!--<button id='stop' class="btn btn-lg btn-primary glyphicon glyphicon-stop"></button>-->
<button id='right' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-right"></button>
</div align="center">
<div align="center">
<button id='down' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-down"></button>
</div>
</td>
</tr>
</table>
<input type="range" min="0.0" max="100.0", style="width:300px";>
</div>
</body>
</html>
分析:
头文件中通过网页链接引入一个css样式文件,以及jquery.js文件。 脚本中首先判断是移动端还是PC端。如果是移动端则注册"touchstart",“touchend”事件,如果是PC端则注册“mousedown”,mouseup事件。 当按键按下时,发送按键的id号到/cmd。当按键释放时,发送停止命令“stop”到/cmd。 网页主体中通过引入mjpgs-streamer的链接引入视频窗口。设置图像大小为高240,宽320。 <img width="320" height="240" src="http://192.168.10.130:8080/?action=stream"> 此ip地址以及端口号须改为树莓派实际的ip地址和端口号。