上两章我们介绍了用webiopi实现网页控制,对网页控制也有了一定的了解。这一章我们介绍通过Python bottle实现网页控制。Bottle是一个非常小巧的微型Python web 框架。
安装 Bottle sudo apt-get install python-bottle 新建一个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 就会显示如下页面。(改变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) @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> |