当初是这样选中课设这样一个题目的,看到有关于Python这样的题目(基于Python的数据采集器),就很想报,刚好同学找的我,想和我一起组个队一起搞毕设,所以我就说要不我们一起报这个(基于Python的数据采集器)题目,后来一拍而成,就妥妥的报了这个课题。所以才有了后来一系列的学习的文章,还有现在这篇文章,基本上在这个项目当中,我是一个技术指导者吧(也没那么牛逼啦,只是我可能比他们早接触编程吧,比我队友懂多了一点吧,我实在是很垃圾啦)。
首先我们决定使用的是一个小车搭载一个摄像头进行识别,最后添加了温湿度提取显示在页面上。
我们使用的是树莓派这款片上系统编程,驱动小车运动,摄像头识别,温度传感器dht22提取温湿度。
首先我们使用Python这样的一个bottle框架来搭建我们的后台服务器,接下来我们来看看他们的基本用法
第一步当然是安装
$ wget http://bottlepy.org/bottle.py
当然如果有安装Python 直接推荐如下安装方法
$ sudo pip install bottle # recommended $ sudo easy_install bottle # alternative without pip $ sudo apt-get install python-bottle # works for debian, ubuntu, ...
windows安装
无论哪种方式, 您都需要安装好 2.5以上 (包含3.x)来运行 bottle 应用. 如果您没有权限在整个系统中安装这些包或者根本不想这么做,那可以先创建 virtualenv :
$ virtualenv develop # Create virtual environment $ source develop/bin/activate # Change default python to virtual one (develop)$ pip install -U bottle # Install bottle to virtual environment
如果您没有安装virtualenv ,可以通过以下方式安装:
$ wget https://raw.github.com/pypa/virtualenv/master/virtualenv.py $ python virtualenv.py develop # Create virtual environment $ source develop/bin/activate # Change default python to virtual one (develop)$ pip install -U bottle # Install bottle to virtual environment
快速入门: “HELLO WORLD”
from bottle import route, run @route('/hello') def hello(): return "Hello World!" run(host='localhost', port=8080, debug=True)
代码很简单,运行这个脚本,访问 http://localhost:8080/hello 您可以在浏览器中看到 “Hello World!” 。 它的运行原理如下:
route() 装饰器为URL地址绑定了一段代码,在这个例子中,我们关联了/hello 地址到 hello() 函数上。 这个叫route的(装饰器的名字) 是Bottle框架的最重要的概念。您可以定义任意多个 route。当浏览器请求一个URL地址,它所关联的函数就会被调用并返回相应的值给浏览器,原理比较简单。
最后一行调用 run() 方法启动一个内置的开发环境用的服务器,运行在 localhost 8080 端口来为请求提供服务,可以按 Control-c来关闭它。以后你切换使用其他服务器,不过目前使用这个内置服务器就够用了,它不需要任何配置,并且以难以置信的无侵害方式使您的应用运行并且用于本地测试。
默认应用
为了简单起见,本教程中的例子都使用module-level route()装饰器来定义route. 它会将这些routes添加到一个全局的 “默认应用”,它是一个Bottle 的实例,当第一次调用 route()时候会自动创建。有很多其他的 module-level 装饰器和函数与默认应用相关,但是如果您喜欢更面向对象的方法和不介意额外的输入,您可以创建一个单独的应用程序对象,而不是使用全局的默认应用:
from bottle import Bottle, run app = Bottle() @app.route('/hello') def hello(): return "Hello World!" run(app, host='localhost', port=8080)
请求路由
@route('/hello') def hello(): return "Hello World!"
route() 装饰器关联了一个URL 地址到一个回调函数,并添加了一个新的 route 到默认应用( default application),一个应用只有一个路由太单调了,因此,我们增加一些 (不要忘记 from bottle import template):
@route('/') @route('/hello/<name>') def greet(name='Stranger'): return template('Hello {{name}}, how are you?', name=name)
动态路由
包含通配符的Route称为动态路由 (对比于静态路由) ,可以同时匹配不止一个 URL地址。一个简单的通配符由一个名称和一对中括号组成 (例如 ) ,接受下一个斜杠(/)前一个多多个字母。例如 路由/hello/ 接受请求/hello/alice和 /hello/bob,但不接受 /hello, /hello/ 或者/hello/mr/smith.
每一个通配符将覆盖URL的一部分作为一个关键字参数,用于请求的回调函数。您可以使用它们轻松地实现基于RESTful的、代码整洁友好的、有意义的url。这里有一些其他的例子,展示他们匹配的url:
@route('/wiki/<pagename>') # matches /wiki/Learning_Python def show_wiki_page(pagename): ... @route('/<action>/<user>') # matches /follow/defnull def user_api(action, user): ...
0.10 版本后的新特性.
使用过滤器是用来定义更具体的通配符,并且/或者 在传递给回调函数之前对参数进行转换。一个过滤通配符声明为name:filter orname:filter:config。可选配置部分的语法取决于所使用的过滤器。
以下是默认实现的过滤器,以后可能会增加更多:
:int matches (signed) digits only and converts the value to integer. :float similar to :int but for decimal numbers. :path matches all characters including the slash character in a non-greedy way and can be used to match more than one path segment. :re allows you to specify a custom regular expression in the config field. The matched value is not modified.
让我们来看看一些实际的例子:
@route('/object/<id:int>') def callback(id): assert isinstance(id, int) @route('/show/<name:re:[a-z]+>') def callback(name): assert name.isalpha() @route('/static/<path:path>') def callback(path): return static_file(path, ...)
您可以实现定制的过滤器,细节请参考 Request Routing .
0.10版本更新.
Bottle 0.10中引入的新规则语法,用来简化一些常见的用例,但是老语法仍然有效,你可以找到很多示例代码还使用它。下面例子描述了他们的差异:
Old Syntax New Syntax :name <name> :name#regexp# <name:re:regexp> :#regexp# <:re:regexp> :## <:re>
在未来的项目中尽量避免使用旧语法,它目前还能使用,但最终会被弃用。
HTTP请求方法
HTTP协议定义了多种请求方法(request methods ,有时被称作“verbs”) 来实现不同的任务。当没有指定方法时,所有的routes路由都默认使用GET方法,这些路由只能匹配GET请求。如果要处理其他的请求如 POST, PUT, DELETE 或者 PATCH, 给route() 增加一个 method 关键字参数,或者使用四个替代装饰器: get(), post(), put(), delete() 或patch().
POST方法通常用于 HTML 表单的提交,下面例子展示了如何使用POST处理一个登录表单:
from bottle import get, post, request # or route @get('/login') # or @route('/login') def login(): return ''' <form action="/login" method="post"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form> ''' @post('/login') # or @route('/login', method='POST') def do_login(): username = request.forms.get('username') password = request.forms.get('password') if check_login(username, password): return "<p>Your login information was correct.</p>" else: return "<p>Login failed.</p>"
ok,这个小车大概就用到这样的一些用法吧,其他可以访问下面这个博客的链接
bottle 博客教程
1 小车行动的程序
主要界面分为前进,后退,左转,右转,停止,五大功能
前台界面使用 bootstrap 布局简单的界面
前台主要代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>遥控树莓派</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="http://code.jquery.com/jquery.js"></script> <style type="text/css"> #go { margin-left: 55px; margin-bottom: 3px; } #back { margin-top: 3px; margin-left: 55px; } .servo { width: 200px; height: 200px; margin-top: 50px; } </style> </head> <body> <div id="container" class="container"> <div> <button id="go" class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-up"></button> </div> <div> <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> <div> <button id='back' class="btn btn-lg btn-primary glyphicon glyphicon-circle-arrow-down"></button> </div> </div> <div class="servo"> <div> <input type="range" id="upd" name="updown" min="10" max="150" step="20"> <label for="upd">左/右</label> </div> <div> <input type="range" id="leftr" name="leftright" min="50" max="150" step="20"> <label for="leftr">上/下</label> </div> </div> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> var flag=true; $(function(){ $("button").click(function(){ //cmd="{cmd:"+this.id+"}" //alert(cmd) $.post("/cmd",this.id,function(data,status){ }); }); }); $(document).keydown(function(event){ if(flag){ if(event.keyCode==38){ $.post("/cmd","go",function(data,status){ console.log("hello"); flag=false; console.log(flag); }); }else if(event.keyCode == 40){ $.post("/cmd","back",function(data,status){ flag=false; }); }else if(event.keyCode == 37){ $.post("/cmd","left",function(data,status){ flag=false; }); }else if(event.keyCode == 39){ $.post("/cmd","right",function(data,status){ flag=false; }); }else{ alert("hello"); } } }); $(document).keyup(function(event){ if(event.keyCode==38){ $.post("/cmd","stop",function(data,status){ flag=true; }); }else if(event.keyCode == 40){ $.post("/cmd","stop",function(data,status){ flag=true; }); }else if(event.keyCode == 37){ $.post("/cmd","stop",function(data,status){ flag=true; }); }else if(event.keyCode == 39){ $.post("/cmd","stop",function(data,status){ flag=true; }); }else{ alert("hello"); } }); $("#upd").on("input propertychange",function(){ $.post("/ser",{val: $("#upd").val(), flag:0},function(data,status){ console.log(data) console.log(status) }); console.log($("#upd").val()); }).on("mouseup",function(){ $.post("/ser",{val: $("#upd").val(), flag:1},function(data,status){ console.log(data) console.log(status) }); }); $("#leftr").on("input propertychange",function(){ $.post("/serud",{val: $("#leftr").val(), flag:0},function(data,status){ console.log(data) console.log(status) }); console.log($("#upd").val()); }).on("mouseup",function(){ $.post("/ser",{val: $("#leftr").val(), flag:1},function(data,status){ console.log(data) console.log(status) }); }); ; </script> </body> </html>
车子的的电机驱动模块是L298N这个模块,
1 他的主电源正极为 12 V 电压
2 他主要有四个引脚分别为 IN1,IN2,IN3,IN4,这里我们链接在树莓派的11,12,13,15号引脚。
3 5V 输入我是从 2 号引脚拉出来的
4 通道A,B 用于连接电机的,有一个是正极输出,一个是负极输出。
接下来就是我们给小车的驱动代码
#!/usr/bin/env python3 # _*_ coding: utf-8 _*_ from bottle import get,post,run,request,template #导入gpio的模块 import RPi.GPIO as GPIO import time #定义小车驱动引脚号 IN1 = 11 IN2 = 12 IN3 = 13 IN4 = 15 #定义引脚号 ServoPinlr = 23 ServoPinud = 26 #设置GPIO口为BOARD编码方式 GPIO.setmode(GPIO.BOARD) #忽略警告信息 GPIO.setwarnings(False) # 初始化摄像头的PWM波 def inits(): global pwm_servo1 global pwm_servo2 GPIO.setmode(GPIO.BOARD) GPIO.setup(ServoPinlr, GPIO.OUT) GPIO.setup(ServoPinud, GPIO.OUT) #设置pwm引脚和频率为50hz pwm_servo1 = GPIO.PWM(ServoPinud, 50) pwm_servo2 = GPIO.PWM(ServoPinlr, 50) pwm_servo1.start(0) pwm_servo2.start(0) inits() # 控制摄像头左右转动 def servo_control1(pos): # 设置PWM 波的占空比 pwm_servo1.ChangeDutyCycle(2.5 + 10 * pos/180) # 控制摄像头上下转动 def servo_control2(pos): # 设置PWM波的占空比 pwm_servo2.ChangeDutyCycle(2.5 + 10 * pos/180) #设置GPIO口的输出方式 def init(): #设置gpio口的模式 注意 GPIO.setmode(GPIO.BOARD) GPIO.setup(IN1,GPIO.OUT) GPIO.setup(IN2,GPIO.OUT) GPIO.setup(IN3,GPIO.OUT) GPIO.setup(IN4,GPIO.OUT) # 左转函数 def left(): GPIO.output(IN1,False) GPIO.output(IN2,False) GPIO.output(IN3,GPIO.HIGH) GPIO.output(IN4,GPIO.LOW) # 右转 def right(): GPIO.output(IN1,GPIO.HIGH) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,False) GPIO.output(IN4,False) # 前进 def go(): GPIO.output(IN1,GPIO.HIGH) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,GPIO.HIGH) GPIO.output(IN4,GPIO.LOW) # 后退 def back(): GPIO.output(IN1,GPIO.LOW) GPIO.output(IN2,GPIO.HIGH) GPIO.output(IN3,GPIO.LOW) GPIO.output(IN4,GPIO.HIGH) #GPIO.cleanup()#释放GPIO口 # 停止 def stop(): GPIO.output(IN1,GPIO.LOW) GPIO.output(IN2,GPIO.LOW) GPIO.output(IN3,GPIO.LOW) GPIO.output(IN4,GPIO.LOW) #GPIO.cleanup()#释放GPIO口 @get("/") def index(): return template("index") @post("/cmd") def cmd(): # 在这里判断要判断前台的按键按下了那个按键 # 根据按键来响应哪些事件 # 获取前台按下按钮传过来的this.id # 根据this.id判断执行那个函数 # 我猜测request对象应该有一个实例化方法可以查询前台post过来的字段的 # print("按下了按钮: "+request.body.read().decode()) init() # 初始化板和引脚 param = request.body.read().decode(); print(param) if param == 'go': go() if param == 'back': back() if param == 'left': left() if param == 'right': right() if param == 'stop': stop() return "OK" @post("/ser") def ser(): try:#舵机left/right #pos = request.body.read().decode(); pos = int(request.forms.get('val')) flag = int(request.forms.get('flag')) if not flag: pwm_servo1.start(0) servo_control1(pos) print(pos) else: pwm_servo1.stop() pwm_servo1.start(0) except KeyboardInterrupt: pass #GPIO.cleanup() @post("/serud") def serud(): try: pos = int(request.forms.get('val')) flag = int(request.forms.get('flag')) if not flag: pwm_servo2.start(0) servo_control2(pos) print(pos) else: pwm_servo2.stop() pwm_servo2.start(0) except KeyboardInterrupt: pass #GPIO.cleanup() run(host="0.0.0.0",port="1025")
小车完成驱动源码已经上传到git
树莓派小车源码
今天把dht22传感器 提取温湿度也完成了
DHT22 温湿度传感器尽管不是使用效率最高的温湿度传感器,但价格便宜被广泛应用。下面我们介绍另一种基于Adafruit DHT 库读取 DHT22 数据的方法。
DHT22 规格
DHT22 有四个引脚,但是其中一个没有被使用到。所有有的模块会简化成3个引脚。
湿度检测范围 : 20-80% (5% 精度)
温度检测范围 : 0-50°C (±2°C 精度)
该厂商不建议读取频率小于2秒,如果这么做数据可能会有误。
硬件连接
需要在电源和数据脚之间串联一个上拉电阻(4.7K-10K),通常情况下,购买DHT11模块的话都自带了这个电阻。不同的模块型号引脚位置略有不同,下面以图上模块为说明:
DHT Pin Signal Pi Pin
1 3.3V 1
2 Data/Out 11 (GPIO17)
3 not used –
4 Ground 6 or 9
数据引脚可以根据你的需要自行修改。
Python 库
D
DHT22 的读取需要遵循特定的信号协议完成,为了方便我们使用Adafruit DHT 库。
软件安装
开始之前需要更新软件包:
sudo apt-get update sudo apt-get install build-essential python-dev
从 GitHub 获取 Adafruit 库:
sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git cd Adafruit_Python_DHT
给 Python 2 和 Python 3 安装该库:
sudo python setup.py install sudo python3 setup.py install
Adafruit 提供了示例程序,运行下面的命令测试。
cd ~ cd Adafruit_Python_DHT cd examples python AdafruitDHT.py 11 17
这两个参数分别表示 DHT22 和数据引脚所接的树莓派 GPIO 编号。成功的话会输出:
Temp=22.0* Humidity=68.0%
如何在其他 Python 程序中使用这个库
参照下面的方法引入 Adafruit 库,然后就可以使用 “read_retry” 方法来读取 DHT11 的数据了:
import Adafruit_DHT # Set sensor type : Options are DHT11,DHT22 or AM2302 sensor=Adafruit_DHT.DHT11 # Set GPIO sensor is connected to gpio=17 # Use read_retry method. This will retry up to 15 times to # get a sensor reading (waiting 2 seconds between each retry). humidity, temperature = Adafruit_DHT.read_retry(sensor, gpio) # Reading the DHT11 is very sensitive to timings and occasionally # the Pi might fail to get a valid reading. So check if readings are valid. if humidity is not None and temperature is not None: print('Temp={0:0.1f}*C Humidity={1:0.1f}%'.format(temperature, humidity)) else: print('Failed to get reading. Try again!')```
最终温湿度检测源码如下
dht22源码
最终的界面如下,界面暂时样式还没有调整好,所以丑