Python-树莓派编程学习指南-二-

106 阅读1小时+

Python 树莓派编程学习指南(二)

原文:Learn Raspberry Pi Programming with Python

协议:CC BY-NC-SA 4.0

五、网络机器人

任何在网上呆过一段时间的人都会告诉你,互联网上有很多信息。根据谷歌的索引,截至 2013 年,共有 40.4 亿个网页,所以今天的网页数量可能会比这多得多。当然,这些网页中有很多可能是猫的图片和色情内容,但也有数以亿计的网页上有信息。有用的信息。据说每一条被数字化的信息都存在于互联网的某个地方。它必须被发现——当互联网看起来有点像图 5-1 时,这不是一件容易的事情。

img/323064_2_En_5_Fig1_HTML.jpg

图 5-1

互联网地图(2013 http://internet-map.net ,鲁斯兰·埃尼科夫)

不幸的是,任何人都不可能下载并阅读他或她感兴趣的所有信息。人类就是没那么快,我们不得不吃饭、睡觉、完成各种低效的、有时令人不快的任务,比如洗澡和工作来谋生。

幸运的是,我们可以给计算机编程,让它做一些我们自己不需要做的枯燥、重复的任务。这是网络机器人的功能之一:我们可以给机器人编程,让它抓取网页,跟踪链接,下载文件。它通常被称为机器人,知道如何编程和使用机器人是一项非常有用的技能。早上醒来时需要股票报告吗?让你的机器人抓取国际索引,并有一个电子表格等着你。需要研究白星航空公司发布在网上的所有乘客名单,寻找你的祖先吗?让你的机器人从谷歌的“白星”开始,遍历所有的链接。或者你可能想找到所有埃德加·爱伦·坡的手稿,这些手稿目前都在公共领域;当你睡觉的时候,一个机器人也可以帮忙。

Python 特别适合做网络机器人的工作,在这种情况下也被称为蜘蛛。有几个模块需要下载,然后你可以编写一个全功能的机器人来执行你的命令,从你给它的任何页面开始。因为遍历网页和下载信息并不是一个非常耗费处理器资源的任务,所以它也非常适合 Raspberry Pi。当你的普通台式计算机处理更困难的计算任务时,Pi 可以处理下载网页、解析文本和跟随链接下载文件所需的少量工作。

Bot 标签

如果你要建立一个有效的网络爬虫,你需要记住的一个因素是机器人礼仪。我不是指在喝下午茶时确保机器人的小指伸展的礼仪。相反,当你设计你的机器人抓取网站时,你应该注意一些细节。

一个是尊重robots.txt文件。大多数网站在网站的根目录中都有这个文件。这是一个简单的文本文件,包含访问机器人和蜘蛛的指令。如果网站的所有者不希望某些页面被抓取和索引,他可以在文本文件中列出这些页面和目录,礼貌的机器人会同意他的请求。文件格式很简单。看起来是这样的:

User-agent: *
Disallow: /examples/
Disallow: /private.html

这个robots.txt文件指定任何机器人(User-agent: *)都不能访问/ examples/文件夹中的任何页面,也不能访问页面private.htmlrobots.txt文件是一种标准机制,网站可以通过它来限制对某些页面的访问。如果你想让你的机器人在所有网站都受欢迎,遵循这些规则是个好主意。我将解释如何去做。如果你选择忽略这些规则,你可以经常期待你的机器人(和所有来自你的 IP 地址的访问)被禁止(和阻止)从有问题的网站。

另一项礼节是控制你的机器人信息请求的速度。因为机器人是计算机,它们访问和下载页面和文件的速度比人类快成百上千倍。出于这个原因,一个机器人完全有可能在如此短的时间内向一个站点发出如此多的请求,以至于它可以使一个配置很差的 web 服务器瘫痪。因此,将您的机器人的页面请求保持在可管理的水平是礼貌的;大多数网站所有者对每秒 10 个页面请求没什么意见——这远远超过了手工处理的速度,但不足以让服务器停机。同样,在 Python 中,这可以通过一个简单的sleep()函数来完成。

最后,伪造您的用户代理身份经常会有问题。用户代理身份标识网站的访问者。Firefox 浏览器有一个特定的用户代理,Internet Explorer 有另一个,而机器人又有另一个。因为有许多网站根本不希望机器人访问或抓取他们的页面,所以一些机器人作者给他们的机器人一个欺诈用户代理,使它看起来像一个正常的网络浏览器。这一点都不酷。你可能永远不会被发现,但这是一个基本的礼仪问题——如果你有你想保密的页面,你也会希望别人尊重你的意愿。为其他网站所有者做同样的事情。这只是成为一个好的机器人作家和网民(网民)的一部分。如果您出于其他目的模拟浏览器,例如站点测试或查找和下载文件(pdf、MP3 等),但不是为了对这些站点进行爬网,则可以模拟浏览器的用户代理。

网络的连接

在我们开始编程我们的蜘蛛之前,你需要了解一点互联网是如何运作的。是的,它基本上是一个巨大的计算机网络,但这个网络遵循一定的规则,使用一定的协议,我们需要利用这些协议在网络上做任何事情,包括使用蜘蛛。

网络通信协议

超文本传输协议(HTTP)是封装大多数常见 web 流量的格式。协议只是两个通信方(在这种情况下是计算机)之间关于如何进行通信的协议。它包括的信息有:数据如何寻址、如何确定传输过程中是否发生了错误(以及如何处理这些错误)、信息如何在源和目的地之间传输以及信息如何格式化。大多数 URL(统一资源定位符)前面的“http”定义了用于请求页面的协议。其他常用的协议有 TCP/IP(传输控制协议/互联网协议)、UDP(用户数据报协议)、SMTP(简单邮件传输协议)和 FTP(文件传输协议)。使用哪种协议取决于多种因素,如流量类型、请求速度、数据流是否需要按顺序提供服务,以及这些数据流的容错能力。

当你用浏览器请求一个网页时,幕后会发生一些好事。假设你在地址栏中输入 http://www.irrelevantcheetah.com 。您的电脑知道它正在使用 HTTP 协议,首先向其本地 DNS(域名系统)服务器发送 www.irrelevantcheetah.com ,以确定它属于哪个互联网地址。DNS 服务器用一个 IP 地址来响应——比如说,192.185.21.158。这是保存该域网页的服务器的地址。域名系统将 IP 地址映射到名称,因为你我记住“ www.irrelevantcheetah.com ”比记住“192.185.21.158”要容易得多

既然您的电脑知道了服务器的 IP 地址,它就会使用三次“握手”来启动与该服务器的 TCP 连接服务器响应,你的计算机请求页面index.html。服务器响应,然后关闭 TCP 连接。

然后,您的浏览器读取页面上的代码并显示出来。如果它需要页面的其他部分,比如 PHP 代码或图像,那么它会向服务器请求这些部分或图像,并显示它们。

网页格式

大多数网页都是 HTML 格式的——超文本标记语言。它是 XML(可扩展标记语言)的一种形式,非常容易阅读和解析,并且可以被大多数计算机理解。浏览器被编程来解释页面的语言,并以某种方式显示这些页面。例如,标签对<html></html>表示页面是 HTML 格式的。<i></i>表示被包围的文本为斜体,而<a></a>表示一个 超链接 ,通常显示为蓝色并带有下划线。JavaScript 被<script type="text/javascript"></script>标签包围着,各种其他更复杂的标签包围着各种语言和脚本。

所有这些标签和格式使得浏览和阅读原始网页变得容易。然而,它们也有一个令人愉快的副作用,那就是让计算机很容易解析这些页面。毕竟,如果你的浏览器不能解码网页,互联网就不会以现在的形式存在。但是你不需要浏览器来请求和阅读网页——只需要在你得到网页后显示它们。您可以编写一个脚本来请求网页,阅读它们,并使用网页信息执行预先编写好的任务——所有这些都无需人工干预。因此,您可以自动执行搜索特定链接、页面和格式化文档的漫长而枯燥的过程,并将其传递给您的 Pi。这就是网络机器人。

请求示例

为了简单起见,我们先说我们已经请求了页面 http://www.carbon111.com/links.html 。该页面的文本非常简单——毕竟,它是一个静态页面,没有花哨的 web 表单或动态内容,看起来很像这样:

<HTML>
<HEAD>
<TITLE>Links.html</TITLE>
</HEAD>
<BODY BACKGROUND="mainback.jpg" BGCOLOR="#000000"
 TEXT="#E2DBF5" LINK="#EE6000" VLINK="#BD7603" ALINK="#FFFAF0">
<br>
<H1 ALIGN="CENTER">My Favorite Sites and Resources</H1>
<br>
<H2>Comix, Art Gallerys and Points of Interest:</H2>
<DL>
<DT><A HREF="http://www.alessonislearned.com/index.html" TARGET="blank">
A Lesson Is Learned...</A>
<DD>Simply amazing! Great ideas, great execution. I love the depth of humanity
these two dig into. Not for the faint-of-heart ;)
.
.
.

依此类推,直到最后结束<HTML>标记。

如果蜘蛛通过 TCP 连接接收这个页面,它首先会知道这个页面是 HTML 格式的。然后,它将学习页面标题,并开始查找它要查找的内容(如. mp3 或。pdf 文件)以及到其他页面的链接,这些将包含在<A></A>标签中。蜘蛛也可以被编程为跟随链接到某个“深度”;换句话说,您可以指定机器人是否应该跟随链接页面的链接,或者是否应该在第二层之后停止跟随链接。这是一个重要的问题,因为如果你编程了太多的层,你的蜘蛛可能最终会搜索(并下载)整个互联网——如果你的带宽和存储空间有限,这将是一个严重的问题!

我们的网络机器人概念

我们的 web bot 背后的概念如下:我们将基于用户输入从某个页面开始。然后,我们将确定我们要查找什么文件—例如,我们要查找什么。公共领域作品的 pdf 文件?我们喜欢的乐队的免费 MP3 怎么样?这个选择也会被编入我们的机器人。

然后,机器人将从起始页面开始,解析页面上的所有文本。它将查找包含在<a href></a>标签(超链接)中的文本。如果超链接以“.”结尾。pdf”或“. mp3”或其他选择的文件类型,我们将调用 wget (一个命令行下载工具)将文件下载到我们的本地目录。如果我们找不到任何链接到我们选择的文件类型,我们将开始跟随我们找到的链接,按照我们预先确定的递归方式重复这些链接的过程。当我们想走多远就走多远时,我们应该有一个目录,里面装满了我们闲暇时可以细读的文件。这就是网络机器人的用途——让电脑完成繁忙的工作,而你却在啜饮玛格丽塔酒,等待享受它的劳动成果。

解析网页

解析指的是计算机“读取”网页时所经历的过程。在最基本的情况下,网页只不过是由比特和字节(一个字节是八个比特)组成的数据流,当被解码时,形成数字、字母和符号。一个好的解析程序不仅可以将数据流重新格式化为正确的符号,还可以读取重新格式化的数据流并“理解”它所读取的内容。web bot 需要能够解析它加载的页面,因为这些页面可能/应该包含指向它被编程来检索的信息的链接。Python 提供了几种不同的文本解析器模块,我鼓励您进行试验,但是我发现最有用的是 Beautiful Soup。

注意

《美丽的汤》以刘易斯·卡罗尔(1855 年)的《素甲鱼之歌》命名:

美丽的汤,如此丰富和绿色

在热腾腾的盖碗里等着!

谁会为了这样的美味不弯腰?

今晚的汤,好喝的汤!

今晚的汤,好喝的汤!

美汤(Python 库)经历了几个版本;在撰写本文时,它的版本是 4.6.0,可以在 Python 2.x 和 3.x 中运行。

Beautiful Soup 的语法非常基本。一旦您通过键入以下命令安装了它

sudo apt-get install python-bs4

在您的终端中,您可以开始在您的脚本中使用它。通过键入python打开 Python 提示符,并尝试键入以下内容:

import BeautifulSoup

如果你得到一个错误信息说No module named BeautifulSoup,你可能正在使用一个老版本的 Beautiful Soup 4。在这种情况下,请键入

from bs4 import BeautifulSoup

然后,继续使用 Python 提示符:

import re
doc = ['<html><head><title>Page title</title></head>',
    '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.',
    '<p id="secondpara" align="blah">This is paragraph <b>two</b>.',
    '</html>']
soup = BeautifulSoup(".join(doc)) #That's two apostrophes, one after another, not a double quote

这将加载名为doc的文件,该文件包含一个网页流的样子——一个长的单字符流。然后,Soup 将这些行加载到一个可以被库解析的文件中。如果您在此时键入print soup,它看起来将与键入print doc的结果相同。

然而,如果你输入

print soup.prettify()

你将得到这个页面,以一种可读性更好的方式重新制作。这只是一个美丽的汤可以做什么的例子;当我们开始编写机器人程序时,我会详细介绍它。

顺便说一下:在前面的例子中导入的re模块用于计算文本中的正则表达式。如果您不熟悉正则表达式,那么它是一种极其通用的方法,可以在文本中进行搜索,并以人类读者可能不会立即看到的方式挑选出字符串和字符序列。正则表达式术语可能看起来像完全的胡言乱语;正则表达式的一个很好的例子是序列(?<=-)\w+,它在连字符后面的字符串中搜索字符序列。要尝试一下,通过键入python打开 Python 提示符,然后键入

import re
m = re.search('(?<=-)\w+', 'free-bird')
m.group(0)

你会得到奖励

bird

虽然正则表达式在查找文本和字符串中的字符序列方面非常有帮助,但它们也不是非常直观,并且远远超出了本书的范围。我们不会在这里花太多时间。你知道它们的存在就够了,如果你对它们感兴趣,你可以花些时间去了解它们。

使用 Python 模块编码

当谈到使用不同的 Python 模块来编写网络蜘蛛时,您有相当多的选择。许多开源蜘蛛已经存在,你可以从它们那里借鉴,但是从头开始编写蜘蛛代码是一个很好的学习经历。

我们的蜘蛛需要做几件事来做我们需要它做的事情。它需要

  • 启动 TCP 连接和请求页面;

  • 解析接收到的页面;

  • 下载它找到的重要文件;和

  • 跟随它遇到的链接。

幸运的是,其中大多数都是非常简单的任务,所以对我们的蜘蛛编程应该相对简单。

使用机械化模块

可能是自动化网页浏览最常用的模块, mechanize 既简单又复杂。它使用简单,只需几行代码就可以设置好,但是它也包含了许多用户没有充分利用的特性。对于网站测试等自动化任务来说,它是一个非常棒的工具:如果你需要用 50 种不同的用户名/密码组合登录一个站点 50 次,然后填写一个地址表单,mechanize 是你的首选工具。它的另一个好处是它在幕后做了大量的工作,例如启动 TCP 连接和与 web 服务器协商,这样您就可以专注于下载部分。

要在脚本中使用 mechanize,您必须首先下载并安装它。如果您一直跟着做,您仍然可以打开 Python 提示符,但是您需要一个常规的命令行界面来完成这个下载和安装过程。这里,您有两个选择:您可以退出 Python 入口模式,或者您可以打开另一个终端会话。如果您希望只打开一个终端会话,可以通过键入Ctrl+d退出当前窗口中的 Python 提示符,这将使您返回到正常的终端提示符。另一方面,如果您选择打开另一个终端会话,您可以让 Python 会话继续运行,到目前为止您输入的所有内容都将保留在内存中。

无论选择哪个选项,在命令行提示符下输入

https://pypi.python.org/packages/source/m/mechanize/mechanize-0.3.6.tar.gz

下载完成后,用

tar -xzvf mechanize-0.3.6.tar.gz

并通过键入以下命令导航到结果文件夹

cd mechanize-0.3.6.tar.gz

然后,输入

sudo python setup.py install

按照任何屏幕上的指示,mechanize 将被安装并准备使用。

用美汤解析

我前面提到过解析;美丽的汤仍然是最好的选择。如果您还没有这样做,请输入

sudo apt-get install python-bs4

让包管理器完成它的工作。之后就可以立即使用了。正如我之前所说的,一旦你下载了这个页面,Beautiful Soup 就负责找到链接,并把它们传递给我们用来下载的函数,同时也负责把后面要用到的那些链接放在一边。

然而,这样做的结果是,查找链接和决定下载什么的工作主要变成了字符串的问题。换句话说,链接(以及其中包含的文本)只不过是字符串,在我们解开这些链接并跟踪它们或下载它们的过程中,我们将对字符串做大量的工作——从lstrip(删除最左边的字符)到appendsplit以及字符串库中的各种其他方法。毕竟,也许网络机器人最有趣的部分不是它下载的文件;更确切地说,这是你必须要做的操作。

使用urllib库下载

这个难题的最后一部分是urllib库——特别是它的URLopener.retrieve()函数。这个功能是用来下载文件的,流畅不忙乱。我们将把我们的文件名传递给它,让它去做它的工作。

要使用urllib,必须先导入。使用 Python 提示符切换到终端,如果它仍然打开的话,或者通过键入python启动另一个会话。然后,键入

import urllib

以使其可供使用。

urllib库使用以下语法:

image = urllib.URLopener()
image.retrieve ("http://www.website.com/imageFile.jpg", "imageFile.jpg")

其中发送给URLopener.retrieve()函数的第一个参数是文件的 URL,第二个参数是文件将要保存的本地文件名。第二,文件名参数遵循 Linux 文件和目录约定;如果你给它参数"../../imageFile.jpg",imageFile.jpg将被保存在目录树中的两个文件夹中。同样,向它传递参数“pics/imageFile.jpg”会将它保存在当前目录(脚本运行的目录)内的pics文件夹中。但是,该文件夹必须已经存在;retrieve()不会创建目录。这是一件需要记住的重要事情,因为它也会无声无息地失败;也就是说,如果该目录不存在,您的脚本将会像一切正常一样执行,然后第二天早上您会发现您下载的这两千条记录中没有一条被保存到磁盘上。

决定下载什么

这可能会有点棘手,因为太多了。不幸的是(或者幸运的是,取决于你的观点),很多都是有版权的,所以即使你发现它是免费的,只是下载它真的不酷。你要找的东西就在外面。

然而,这是另一本完全不同的书的主题。目前,让我们假设你要寻找免费的信息,比如马克·吐温在公共领域的所有作品。这意味着你可能要寻找。pdf,。txt,甚至可能。医生或。docx 文件。你甚至可能想扩大你的搜索参数包括。mobi (Kindle)和。epub 文件,以及. chm. (chm 代表编译的 HTML,微软在他们的 HtMl 格式的帮助程序中使用它,它也经常用于基于 web 版本的教科书。)这些都是合法的文件格式,可能包含您正在寻找的书籍的文本。

选择起点

接下来你需要的是一个起点。你可能倾向于说“谷歌!“但是,对于一个简单的搜索“马克·吐温”会有数以千万计的搜索结果,你可能会更专注一点。提前做一些基础工作,这样可以节省你(和你的机器人)的工作时间。例如,如果你能找到吐温作品的在线档案,那将是一个很好的起点。如果你正在寻找免费的音乐下载,你可能想得到一个收录了崭露头角的乐队的新音乐文件的博客列表,因为许多新艺术家在这些博客上提供免费的歌曲下载,以推广他们自己和他们的音乐。同样,关于 IEEE 网络规范的技术文档可能在一个技术网站上找到,甚至是一个政府网站,比广泛的谷歌搜索更成功(和更集中的结果)。

存储您的文件

您可能还需要一个地方来存储您的文件,这取决于您的 Pi 的 SD 卡的大小。该卡既充当 RAM,也是文件存储的地方,所以如果你使用 32GB 的卡,你会有很多空间。pdf 文件。然而,如果你正在下载免费的纪录片文件,8GB 的卡可能会很快就满了。因此,你需要一个外置 USB 硬盘,要么是一个完整的硬盘,要么是一个较小的闪存驱动器。

同样,这也是一些实验可能派上用场的地方,因为一些外部驱动器不能很好地与 Raspberry Pi 一起工作。因为现在它们不是特别贵,我会买一两个中等大小的试一试。我目前正在使用戴恩-ELEC 的 8GB 闪存驱动器(如图 5-2 所示),没有任何问题。

img/323064_2_En_5_Fig2_HTML.jpg

图 5-2

用于存储文件的通用闪存驱动器

关于通过命令行访问你的跳转驱动器的注意事项:在/media目录中可以访问连接的驱动器,例如闪存驱动器;也就是说,

cd /media

将带您到您应该看到您的驱动器列表的目录。然后,您可以导航到其中并访问其内容。您需要设置您的 Python 脚本来将文件保存到那个目录中,例如/media/PENDRIVE/media/EnglebertHumperdinckLoveSongs。也许最简单的方法是将你的webbot.py脚本保存在你的外部驱动器上,然后从那里运行它。

编写 Python 机器人

让我们开始写一些 Python。下面的代码导入了必要的模块,并使用 Python 版本的 input ( raw_input)来获得一个起点(在这个起点上,我已经添加了在每个网址中都可以找到的http://)。然后它用mechanize.Browser()启动一个“浏览器”(带引号)。本章末尾列出了最终完成的代码。它也可以作为webbot.pyapress.com网站下载。

要开始编写您的机器人,请使用您的文本编辑器创建一个名为webbot.py的新文件。输入以下内容:

from bs4 import BeautifulSoup
import mechanize
import time
import urllib
import string

start = "http://" + raw_input ("Where would you like to start searching?\n")
br = mechanize.Browser()
r = br.open(start)
html = r.read()

稍后,我们可能需要伪造一个用户代理,这取决于我们访问的站点,但是这段代码现在可以用了。

读取字符串并提取所有链接

一旦你有了一个 browser 对象,在前面的代码中叫做br,你就可以用它来做各种各样的任务。我们使用br.open()打开用户请求的起始页,并将其读入一个长字符串html。现在,我们可以使用 Beautiful Soup 来读取该字符串,并通过添加以下行来从中提取所有链接:

soup = BeautifulSoup(html)
for link in soup.find_all('a'):
    print (link.get('href'))

现在,运行脚本来进行试验。保存并关闭它。打开一个终端会话,导航到您创建webbot.py的目录。然后打字

python webbot.py

启动程序,并在程序询问从哪里开始时键入example.com。它应该返回以下内容,然后退出:

http://www.iana.org/domains/example

您已经成功地阅读了 http://example.com 的内容,提取了链接(只有一个),并将该链接打印到屏幕上。这是一个令人敬畏的开始。

下一个合乎逻辑的步骤是实例化一个链接列表,每当 Beautiful Soup 找到另一个链接时就添加到这个列表中。然后可以遍历列表,用另一个浏览器对象打开每个链接,重复这个过程。

查找和下载文件

然而,在实例化这个链接列表之前,我们还需要创建一个函数——实际查找和下载文件的函数!因此,让我们在页面上搜索文件类型的代码。我们可能应该回过头来,通过在脚本的开头、start 行之后添加下面的代码行,来询问我们正在寻找什么类型的文件:

filetype = raw_input("What file type are you looking for?\n")

注意

如果您想知道,在这两种情况下,raw_input字符串末尾的\n都是回车符。显示该行时,它不会被打印出来。相反,它会将光标移动到下一行的开头,等待您的输入。这是不必要的—它只是让输出看起来更漂亮一点。

现在我们知道我们在寻找什么,当我们把每个链接添加到列表中时,我们可以检查它是否是一个我们想要的文件的链接。如果我们要找。例如,pdf 文件,我们可以解析链接,看看它是否以 pdf 结尾。如果是,我们将调用URLopener.retrieve()并下载文件。因此,再次打开您的webbot.py副本,并用以下代码替换for代码块:

for link in soup.find_all('a'):
    linkText = str(link)
    if filetype in linkText:
        # Download file code here

您会注意到这段代码中的两个元素。首先,添加了str(link)位。Beautiful Soup 为我们找到了页面上的每个链接,但是它将链接作为一个链接对象返回,这对于非 Soup 代码来说是没有意义的。我们需要将它转换成一个字符串,以便使用它并进行我们所有的巧妙操作。这就是调用str()方法的作用。事实上,Beautiful Soup 为我们提供了一种方法,但是学习用str()函数解析字符串是很重要的。事实上,这就是我们在代码开头使用行import string的原因——这样我们就可以与字符串对象交互。

第二,一旦链接是一个字符串,你可以看到我们如何使用 Python 的in调用。类似于 C#的String.contains()方法,Python 的in调用只是搜索字符串,看它是否包含请求的子串。在我们的案例中如果我们要找的是。pdf 文件,我们可以在链接文本中搜索子字符串“pdf”如果有,那就是我们感兴趣的链接。

测试机器人

为了让测试我们的机器人更容易,我在 http://www.irrelevantcheetah.com/browserimages.html 设置了一个页面用于测试。它包含图像、文件、链接和各种其他 HTML 好东西。使用这个页面,我们可以从一些简单的东西开始,比如图像。因此,让我们修改我们的webbot.py代码,使它看起来像这样:

import mechanize
import time
from bs4 import BeautifulSoup
import string
import urllib
start = "http://www.irrelevantcheetah.com/browserimages.html"
filetype = raw_input ("What file type are you looking for?\n")
br = mechanize.Browser()
r = br.open(start)
html = r.read()
soup = BeautifulSoup(html)

for link in soup.find_all('a'):
    linkText = str(link)
    fileName = str(link.get('href'))
    if filetype in fileName:
        image = urllib.URLopener()
        linkGet = http://www.irrelevantcheetah.com + fileName
        filesave = string.lstrip(fileName, '/')
        image.retrieve(linkGet, filesave)

我认为,从for循环开始的这最后一段代码需要一些解释。for循环遍历 Beautiful Soup 为我们找到的所有链接。然后,linkText将这些链接转换成字符串,这样我们就可以操作它们了。然后我们将链接的主体(链接指向的实际文件或页面)也转换成一个字符串,并检查它是否包含我们正在寻找的文件类型。如果是的话,我们把它附加到站点的基本 URL,给我们linkGet

由于retrieve()函数,最后两行必须发生。正如您所记得的,该函数有两个参数:我们正在下载的文件的 URL 和我们想要保存该文件的本地名称。filesave获取我们之前找到的fileName,并从名称中删除前面的/,以便我们可以保存它。如果我们不这样做,我们试图保存的fileName将会是——例如—img/flower1.jpg。如果我们试图用那个名字保存一个图像,Linux 会尝试将flower.jpg保存到/images文件夹,然后给我们一个错误,因为/images文件夹不存在。通过去掉前面的“/”,fileName变成了images/flower1.jpg,只要我们当前的目录中有一个images文件夹(记住我说的先创建目录),文件就会被保存,不会发生任何事件。最后,最后一行代码使用我已经提到的两个参数进行实际下载:linkGetfilesave`。

如果您在当前目录中创建一个images目录,然后运行这个脚本,对文件类型问题回答“jpg”,images目录应该会充满 12 个不同的美丽花朵的图像,这些图像是您真正亲手挑选的。简单,嗯?相反,如果你创建一个files目录并回答“pdf”,你会在你的files文件夹中得到 12 个不同的(无聊的)pdf。

创建目录和实例化列表

我们还需要添加两个特性来完成这个机器人。首先,我们并不总是能够提前知道需要创建什么目录,所以我们需要找到一种方法,从链接文本中解析文件夹名称,并动态地创建目录。其次,我们需要创建一个链接到其他页面的链接列表,这样我们就可以访问这些页面并重复下载过程。如果我们这样做几次,我们就有了一个真正的网络机器人,跟随链接并下载我们想要的文件。

让我们首先执行第二项任务——实例化我们前面提到的链接列表。我们可以在脚本的开头、导入语句之后创建一个列表,并在执行过程中添加到列表中。要创建一个列表,我们只需使用

linkList = []

为此,我们在脚本中添加了一个elif块:

if filetype in fileName:
    image = urllib.URLopener()
    linkGet = http://www.irrelevantcheetah.com + fileName
    filesave = string.lstrip(fileName, '/')
    image.retrieve(linkGet, filesave)

elif "htm" in fileName: # This covers both ".htm" and ".html" filenames
    linkList.append(link)

就这样!如果fileName包含我们正在寻找的链接类型,它将被检索。如果没有,但其中有一个htm,它会被追加到linkList——一个列表,然后我们可以一个接一个地迭代,打开每个页面并重复下载过程。

我们将多次重复下载过程的事实应该让你想到编码的一个元素:一个函数——也称为方法。请记住,如果有一个过程需要你一遍又一遍地重复,那么在代码中就会用到函数。它使代码更干净、更简单,也更容易编写。你会发现,程序员是非常高效的人(有人会说他们懒惰)。如果我们可以编写一次代码并重用它,这比一遍又一遍地输入要好得多。这也是一个巨大的时间节省。

因此,让我们通过在我们刚刚添加的linkList = []行之后,向我们的webbot.py脚本添加以下行来开始我们的下载功能:

def downloadFiles (html, base, filetype, filelist):
    soup = BeautifulSoup(html)
    for link in soup.find_all('a'):
        linkText = str(link.get('href'))
        if filetype in linkText:
            image = urllib.URLopener()
            linkGet = base + linkText
            filesave = string.lstrip(linkText, '/')
            image.retrieve(linkGet, filesave)
        elif "htm" in linkText:  # Covers both "html" and "htm"
            linkList.append(link)

现在我们有了我们的downloadFiles函数,我们所要做的就是解析我们的linkText来获得我们需要创建的目录的名称。

同样,这是简单的字符串操作,并使用了os模块。os模块允许我们操作目录和文件,不管我们运行的是什么操作系统。首先,我们可以添加

import os

添加到我们的脚本中,然后我们可以通过添加

os.makedirs()

您可能还记得,为了简化文件保存,我们需要在机器上有一个本地目录,该目录与存储目标文件的 web 目录相匹配。为了查看我们是否需要一个本地目录,我们需要首先确定该目录的名称。在大多数(如果不是全部)情况下,该目录将是我们的linkText的第一部分;例如 img/picture1.html中的目录名是images。所以,第一步是再次迭代linkText`,寻找斜线,就像我们获得网站名称的基础一样,就像这样:

slashList = [i for i, ind in enumerate(linkText) if ind == '/']
directoryName = linkText[(slashList[0] + 1) : slashList[1]]

前面的代码创建了一个索引列表,在这个列表中可以找到字符串linkText中的斜杠。然后,directoryNamelinkText切割到前两个斜线之间的部分 img/picture1.html被切割到images`)。

这段代码的第一行有一些解释,因为它是重要的一行代码。linkText是一个字符串,因此是可枚举的;也就是说,其中的字符可以一个接一个地迭代。slashList是斜线所在的linkText中的位置(索引)列表。在第一行填充了slashList之后,directoryName简单地抓取包含在第一个和第二个斜杠之间的文本。

接下来的两行只是检查是否存在匹配directoryName的目录;如果没有,我们就创建它:

if not os.path.exists(directoryName):
    os.makedirs(directoryName)

这就完成了我们的downloadProcess功能,同时也完成了我们简单的网络机器人。试一试,把它指向 http://www.irrelevantcheetah.com/browserimages.html ,询问 jpg、pdf 或 txt 文件类型,然后看它创建文件夹和下载文件——所有这些都不需要你的帮助。

既然你有了这个想法,你就可以为之疯狂了!创建目录,冲浪三个(或更多)层次深,看看你的机器人为你下载,而你不看!一半的乐趣是有时在你最意想不到的时候看到什么被下载了!

最终代码

在这里,你可以看到你一点一点输入的最后的冗长的代码,如果你一直跟着我们学习这一章的话。同样,如果你不想全部输入,它可以在 Apress.com 上以webbot.py的名称下载。然而,我高度推荐你键入代码,因为如果你键入代码,比简单地复制粘贴会更有效。我的一位教授曾经说过,把代码输入进去,你就拥有了自己的代码。

import mechanize
import time
from bs4 import BeautifulSoup
import re
import urllib
import string
import os

def downloadProcess(html, base, filetype, linkList):
    "This does the actual file downloading"
    Soup = BeautifulSoup(html)
    For link in soup.find('a'):
        linkText = str(link.get('href'))
        if filetype in linkText:
            slashList = [i for i, ind in enumerate(linkText) if ind == '/']
            directoryName = linkText[(slashList[0] + 1) : slashList[1]]
            if not os.path.exists(directoryName):
                os.makedirs(directoryName)

            image = urllib.URLopener()
            linkGet = base + linkText
            filesave = string.lstrip(linkText, "/")
            image.retrieve(linkGet, filesave)
        elif "htm" in linkText:  # Covers both "html" and "htm"
            linkList.append(link)

start = "http://" + raw_input ("Where would you like to start searching?\n")
filetype = raw_input ("What file type are you looking for?\n")

numSlash = start.count('/') #number of slashes in start—need to remove everything after third slash
slashList = [i for i, ind in enumerate(start) if ind == '/'] #list of indices of slashes

if (len(slashList) >= 3): #if there are 3 or more slashes, cut after 3
    third = slashList[2]
    base = start[:third] #base is everything up to third slash
else:
    base = start

br = mechanize.Browser()
r = br.open(start)
html = r.read()
linkList = [] #empty list of links

print "Parsing" + start
downloadProcess(html, base, filetype, linkList)

for leftover in linkList:
    time.sleep(0.1) #wait 0.1 seconds to avoid overloading server
    linkText = str(leftover.get('href'))
    print "Parsing" + base + linkText
    br = mechanize.Browser()
    r = br.open(base + linkText)
    html = r.read()
    linkList = []
    downloadProcess(html, base, filetype, linkList)

摘要

在这一章中,你通过编写一个网络机器人(或蜘蛛)很好地了解了 Python,它可以为你遍历互联网并下载你感兴趣的文件,甚至可能在你睡觉的时候。您使用了一两个函数,构造并添加了一个列表对象,甚至做了一些简单的字符串操作。

在下一章,我们将离开数字世界,与一种非常物理的现象——天气——互动。

六、气象站

自古以来,人类就对天气着迷,问诸如“会不会下雨给我们的庄稼?会下雪吗,这样我们就可以去滑雪了?龙卷风会把我们的房子带到一个虚构的国家,那里住着超自然的女人和会飞的灵长类动物吗?我们每天都有某种天气——今天会是什么天气?”

预测天气并不总是一种科学追求。人们会向雨神祈求下雨,向太阳神祈求阳光。如果祈祷不起作用,他们会经常拜访一位先知或预言家,他们声称有能力预见未来并预测即将到来的低压系统的路径(当然,不是用那些特定的词语)。

渐渐地,天气背后的科学被发现了,我们不再需要依赖一块神奇的石头来预测天气(见图 6-1 )。人们上学是为了成为气象学家,了解天气前沿、风暴潮和其他与天气相关的科学信息。

img/323064_2_En_6_Fig1_HTML.jpg

图 6-1

气象石(图片 2010 汤姆·纳普)

为了所有这些进步,需要一个气象站——一个小型的、局部的跟踪当前状况的方法。即使是小型气象站通常也会给出风速和风向、温度、湿度和相对气压。这些读数中的每一个,在一两天的时间里结合起来看,都可以帮助你预测不久的将来的天气。

当然,Raspberry Pi 非常适合创建这个气象站应用。大量的计算能力不是所需要的,但是与小型传感器网络轻松互动的能力是需要的。一些通过 I2C(内部集成电路)连接到 Pi,一些通过脉宽调制(PWM)连接,一些简单地连接到 GPIO 引脚。通过以循环方式逐一轮询每个传感器,我们可以获得任何给定时刻天气状况的准确图像。

让我们从收集建造气象站所需的零件开始。

零件购物清单

气象站不涉及很多部分,但公平的警告:考虑到它们的大小,其中一些比你想象的要贵一点:

使用 I2C 协议

这个项目利用 I2C 协议与您将添加到 Pi 中的湿度和压力传感器进行通信。虽然这是一个相对简单的协议,但它可能会有点混乱,所以在我们开始构建站点之前,最好快速地回顾一下。

I2C 使大量设备能够在一条电路上只用三根线进行通信:数据线、时钟线和地线。每个设备称为一个节点,通常有一个主节点和多个从节点。每个从机节点都有一个 7 位地址,如 0x77 或 0x43。当主节点需要与某个从节点通信时,它首先在数据线上发送一个“起始”位,然后是从节点的地址。该从机做出应答,而所有其它从机忽略消息的其余部分,继续等待下一个地址脉冲的发送。然后,主机和从机相互通信,经常在发送和接收模式之间切换,直到所有信息都发送完毕。

I2C 被称为“类固醇上的串行协议”,它最常用于速度无关紧要且需要保持低成本的应用中。Raspberry Pi 有两个引脚#3 和#5,分别预配置为 I2C 协议的 SDA(数据)和 SCL(时钟)线路,因此它可以轻松地与 I2C 设备通信。我们将使用的两种设备(气压计/高度计和磁力计)是 I2C 设备。

Pi 还有一个 I2C 实用程序,可以查看当前连接的设备。要安装它,请键入

sudo apt-get install python-smbus
sudo apt-get install i2c-tools

如果你使用的是 Raspbian 的最新版本,比如 Wheezy 或 Stretch,这些应该已经安装了,在这种情况下,你只会得到一个提示,告诉你它们已经是最新版本了。

现在,您可以运行名为 i2cdetect 的 I2C 实用工具来确保一切正常,并查看连接了哪些设备。类型

sudo i2cdetect -y 1

这将显示如图 6-2 所示的屏幕。

img/323064_2_En_6_Fig2_HTML.jpg

图 6-2

i2cdetect工具

在这种情况下,不存在任何设备,这是有意义的,因为我们还没有插入任何设备。但是您现在知道您的工具运行正常。

使用风速计

任何气象站的重要组成部分都是风速计——测量风速的设备——因为风速是任何天气预报的重要因素。如果是寒冷的一天(例如,低于 32 华氏度或 0 摄氏度),风速在感觉有多冷(风寒)方面起着重要作用。根据国家气象局的风寒图表,15 华氏度时 15 英里/小时的风让人感觉像 0 华氏度,0 华氏度时 20 英里/小时的风让人感觉像零下 24 度。在这种情况下,风速对于决定你的四肢是先冻僵还是先掉下来是很重要的(如果你问我,我会说两者都不吸引人)。

另一方面,如果外面不是特别冷,风速对下一个天气现象的到来速度有影响。以每小时 2 英里的速度,阳光灿烂的日子还要过几天才能到达你的身边;以每小时 50 英里的速度,在龙卷风摧毁你的房子之前,你只有几分钟的时间了。

风速计可能是一个相当复杂的装置,有轴承、轴和开关等等;另一方面,我们的相对简单。

制作风速计

我们将使用旋转轴编码器、旋转轴和一些鳍片来测量风速。

我们使用的 Vex robotics 的旋转轴编码器由一个塑料圆盘组成,圆盘周围有均匀分布的狭缝。当通电时,一束微弱的光线穿过光盘上的缝隙,照射到另一面的光敏接收器上。通过计算在给定时间间隔内光线被光盘阻挡的次数(或者,光线穿过狭缝的次数),可以确定光盘旋转的速度。也可以确定光盘已经旋转了多少次,事实上,这就是旋转编码器经常使用的方式。例如,如果一个旋转编码器被挂在机器人的轴上,这是一个非常好的方法来测量连接到那个轴上的轮子已经行驶了多远。如果光盘有 90 个狭缝(就像我们的一样),我们知道轴旋转一整圈(车轮旋转一整圈)就是编码器的光接收器上闪烁 90 次。因此,我们可以告诉机器人,“向前开 30 条缝”,轮子将向前推进其周长的三分之一。如果我们知道圆盘/轮子的周长是 3 英尺,我们就知道机器人刚刚前进了 1 英尺。

这看起来像是许多不必要的数学,但是理解编码器如何工作是很重要的。一旦我们将翼片连接到旋转轴上,我们就可以(理论上)根据翼片的周长和轴的速度计算出风速。然而,我的经验是,实际上用已知的风速进行实验并把这些速度纳入我们的程序要容易得多,所以这就是我们要做的。要做到这一点,你需要一个搭档——在你测量风速的时候,他能以预定的、正常的速度载着你到处跑。这意味着大约 5-20 英里每小时的速度,不是 80。

要制作你的风速计,仔细阅读你当地的五金店,直到你找到一个 1/8 英寸的方形小轴,它将适合旋转编码器的方孔(见图 6-3 )。

img/323064_2_En_6_Fig3_HTML.jpg

图 6-3

方杆

注意

在撰写本文时,一个 1/8 英寸的轴完全适合旋转编码器的孔。

接下来,你需要一个风车或类似的东西。我使用了一个科学工具包中的风车部分,你可以在当地的工艺品商店买到(例如, http://amzn.to/1koelSW )。正如你所看到的,轴完美地安装在风车的孔中,定向鳍很容易连接到编码器的背面。(见图 6-4 。)

img/323064_2_En_6_Fig4_HTML.jpg

图 6-4

附有风向标的编码器

整个机构需要旋转;也就是说,它需要连接到一个可以在轴上旋转的设备上,比如风向标,这样我们就可以确定风的方向。这就是懒苏珊轴承组发挥作用的地方。

首先,在 PVC 管的末端切两个槽,您的编码器将紧密地安装在其中(如图 6-5 所示)。

img/323064_2_En_6_Fig5_HTML.jpg

图 6-5

PVC 管插槽中的编码器

将 PVC 盖放在管子的另一端。将一个轻木块连接到转盘轴承的一侧,然后,尽可能靠近旋转轴的中间,从下面用一个螺钉连接 PVC 管和盖子。图 6-6 显示了确定旋转轴中心的一种方法。

img/323064_2_En_6_Fig6_HTML.jpg

图 6-6

确定平台中心

完成后,你应该有一个如图 6-7 所示的装配。

img/323064_2_En_6_Fig7_HTML.jpg

图 6-7

纳米组件

将风速计连接到 Pi

现在,我们需要将风速计连接到 Pi 并测量转速。将编码器的红线连接到 Pi 的电源引脚(#2),黑线连接到 GND (#6),白线连接到您选择的 GPIO 引脚。为了便于说明,我们使用 8 号引脚。

如前所述,这种编码器的工作原理是,每当光盘中的一个狭缝通过某个点时,就发送一个高信号。我们知道圆盘上有 90 个狭缝,所以每 90 个高信号等于轴旋转一周。我们所要做的就是记录高点,以及获得 90 个高点需要多长时间,这样我们就可以得到一段时间内的旋转速度。如果我们以秒为单位跟踪时间(当我们使用时间库时就会这样),我们就会得到每秒转数。因此,读取编码器的代码应该是这样的:

import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

prev_input = 0
total = 0
current = time.time()

while True:
    input = GPIO.input(8)
    if ((not prev_input) and input):
        print ("turning")
        total = total + 1
    prev_input = input
    if total == 90:
        print (1/(time.time() - current)), "revolutions per sec"
        total = 0
        current = time.time()

这里所有有趣的事情都发生在while循环中。由于我们已经开始将prev_input设置为0,作为输入的1 ( HIGH)意味着圆盘正在转动。在这种情况下,我们增加total,将prev_input设置为input,并在检查是否达到 90 高点后继续循环。如果有,这意味着我们正好转了一圈,所以我们可以计算和打印每秒转数(RPS ),并重置总转数和当前转数。要测试这个编码器代码,请将电线连接到您的 Pi,运行脚本,并手动旋转编码器轮。您应该看到单词“turning”重复了 90 次,然后显示了一行 RPS。

将每秒转数与风速相关联

如果编码器正常工作,剩下的唯一步骤就是将每秒转数与风速相关联,而最简单的方法就是与朋友和汽车一起完成。将您的风速计伸出窗外,将您的 Pi 通过 ad-hoc 网络连接到您的笔记本电脑(见侧栏),让您的朋友以 5 英里/小时的速度驾驶几分钟,同时您运行编码器脚本;以每小时 10 英里、15 英里和 20 英里的速度重复这个过程,直到你有足够的数据将风速与 RPS 关联起来。

当我把风速计挂在窗外开车时,我得到了表 6-1 中显示的 RPS 读数。

表 6-1

使用风速计,MPH 与 RPS 读数相关

|

每小时英里数

|

RevolutionsPerSecond 每秒的转速

| | --- | --- | | five | Five point eight | | Ten | Nine point two three | | Fifteen | Ten point eight | | Twenty | Eleven point seven |

MPH 和 RPS 的相关性很明显是对数关系,也就是说我们可以用一点代数(eek!)根据每秒转数计算风速。

如果你把这些值绘制在图上,你会得到图 6-8 。

img/323064_2_En_6_Fig8_HTML.jpg

图 6-8

RPS 与 MPH

从等式中可以看出,每秒转数和风速之间的关系是对数关系,而不是线性关系。因此,我们必须使用反对数函数,或 e x ,以每秒转数来求解风速。我不想用数学来烦你,所以请相信我的话

  • 风速= e ((y+0.95)/4.3)

你很快就会看到,我们将能够把那个计算代入我们的最终程序。

通过点对点网络将 Pi 连接到笔记本电脑

如果你和我一样,我用我的 Pi 做的大部分工作都是无头的——如果我需要查看桌面,我会 SSH(安全外壳)进入它或运行 VNC(虚拟网络计算)服务器,但我通常没有连接到它的显示器、鼠标或键盘。举例来说,如果你连接到你的家庭网络,这很好,但是如果周围没有网络呢?幸运的是,在您的 Pi 和笔记本电脑之间建立一个有线的 ad-hoc 网络非常简单。点对点只是 Pi 和另一台计算机(如您的笔记本电脑)之间的网络连接,中间没有路由器或集线器。

最简单的设置方法是记下您的 Pi 的静态 IP 地址,并调整您的笔记本电脑的以太网端口以与该地址通信。假设您的 Pi 的地址是 192.168.2.42。使用短以太网电缆将您的 Pi 直接连接到笔记本电脑的以太网端口。现在,进入你的笔记本电脑的网络设置。您的计算机很可能设置为通过 DHCP(动态主机控制协议)自动从路由器接收地址。将该方法更改为 Manual,并为您计算机的网络端口分配一个与 Pi 的子网一致的地址。在我们的例子中,一个好的地址应该是 192.168.2.10。如果有合适的位置,请填写子网掩码(255.255.255.0 将在本例中起作用)和默认网关(本例中为 192.168.2.1)。如有必要,重新启动计算机或重启网络管理器。

现在,您应该能够通过标准终端连接登录到直接连接的 Pi:

ssh -l pi 192.168.2.42

您可以像在家庭网络上一样工作。

连接数字指南针

我们将在这个项目中使用的数字罗盘只有一个目的:让我们知道风向。我们正在使用的 HMC5883L 使用 I2C 协议,所以在继续之前,请确保您熟悉本章前面的“使用 I2C 协议”一节中的信息。

首先将附带的插头焊接到 HMC 分线板。取向由你决定;如果你打算把它做成独立的,你可能想让标题朝上,以便于访问。另一方面,如果你打算将芯片插入试验板,尽一切办法将它们朝下焊接,这样你就可以轻松地将整个单元插入电路板。

接头焊接到电路板后,用跳线将引脚连接到您的 Pi。VCC 和 GND 分别连接到 Pi 的#2 和#6 引脚,SDA 和 SCL 连接到 Pi 的#3 和#5 引脚。您现在已经准备好使用smbus库来读取指南针,使用一点数学(eek!)以基于感测到的 xy 值来计算方位。现在是使用之前提到的i2cdetect工具的好时机,以确保你可以从指南针上读取。通过键入sudo i2cdetect -y 1运行该工具,您应该看到芯片列出了地址0x1e(参见图 6-9 )。

img/323064_2_En_6_Fig9_HTML.jpg

图 6-9

查看指南针的 I2C 地址

如果没有出现,请仔细检查您的连接。(你看到的图 6-9 、0x60中列出的另一个地址是我插入 Pi 的另一个 I2C 设备。)当它出现时,启动一个新的 Python 脚本从设备中读取。我们将使用smbus库的 I2C 工具来读写传感器。首先,在 Pi 上创建一个目录,将所有气象站代码保存在一起,输入

cd ~
mkdir weather
cd weather

现在您已经在您的主文件夹中创建了一个weather目录,并在其中导航,在您的新 Python 脚本中键入以下代码:

import smbus
import math

bus = smbus.SMBus(0)
address = 0x1e

def read_byte(adr):
    return bus.read_byte_data(address, adr)
def read_word(adr):
    high = bus. read_byte_data(address, adr)
    low = bus.read_byte_data(address, adr+1)
    val = (high << 8) + low
    return val

def read_word_2c(adr):
    val = read_word(adr)
    if (val >= 0x8000):
        return -((65535 - val) + 1)
    else:
        return val

def write_byte(adr, value):
    bus.write_byte_data(address, adr, value)

write_byte (0, 0b01110000)
write_byte (1, 0b00100000)
write_byte (2, 0b00000000)

scale = 0.92
x_offset = -39
y_offset = -100

x_out = (read_word_2c(3) - x_offset) * scale
y_out = (read_word_2c(7) - y_offset) * scale

bearing = math.atan2(y_out, x_out)
if bearing < 0:
    bearing += 2 * math.pi
print "Bearing:", math.degrees(bearing)

导入正确的库后,该脚本使用smbus库设置读取和写入传感器地址的函数。函数read_byte()read_word()read_word_2c()write_byte()均用于读取和写入传感器 I2C 地址的值(单字节或 8 位值)。三条write_byte()线将值 112、32 和 0 写入传感器,以对其进行读取配置。这些值通常列在 I2C 传感器随附的数据表中。

注意

您可能已经注意到,当您从 Adafruit 或 Sparkfun 购买分线板时,这些公司通常会提供该传感器的示例代码。每当您从他们那里购买零件时,请查看每个网站上的“文档”链接。正如任何程序员都会告诉你的:如果工作已经完成,就没有必要重新发明轮子。如果已经存在的代码可以解决你的问题,那么使用它也没有什么不好。随着你编程技能的进步,也许用不了多久就会为创客社区贡献代码,解决别人的问题!

然后,脚本读取指南针的 x -和y-轴读数的当前值,并使用数学库的atan2()(反正切)函数计算传感器的方位,首先使用数学库的degrees()函数将其转换为度数。然而,x_offsety_offset的值可能会根据您当前的地理位置而发生变化,确定这些值的最佳方式就是简单地运行脚本。

运行脚本,最好附近有一个工作的指南针,并将您获得的读数与指南针读数进行比较。(带有焊接接头的电路板一侧是电路板“指向”的方向。)您可能需要一点一点地调整偏移,以使轴承正确注册。一旦它被配置好,你就有办法测量风的方向;我们将把指南针安装到风速计的旋转轴上,这样当我们组装最终的气象站时就可以读取方向。

连接温度/湿度传感器

我们正在使用的温度和湿度传感器 Sensirion SHT15 是该建筑中价格较高的部件之一。然而,它也很容易使用,因为不涉及 I2C 协议。您首先需要将附带的接头焊接到它上面。像指南针一样,标题的方向由您决定。我倾向于将电路板朝上焊接接头,这样当我将跳线插入时,我可以看到每个引脚。当然,如果我要把这个单元插入试验板,这意味着我不能读取引脚,但这是一个权衡。

焊接接头后,完成以下步骤:

  1. 将 VCC 引脚连接到 Pi 的 5V 引脚(#2)。

  2. 将 GND 引脚连接到 Pi 的 6 号引脚。

  3. 将 CLK 销连接至 7 号销。

  4. 将数据引脚连接到引脚#11。

注意

随着引脚标记为数据和 CLK,这将是一个可以理解的错误,认为这个板运行在 I2C 协议,但事实并非如此。这些针就是这样标记的。

要使用这个传感器,你必须安装 Luca Nobili 的rpiSht1x Python 库。在您的气象目录中(或者您正在处理气象站代码的任何地方),通过键入以下命令下载rpiSht1x

wget http://bit.ly/1i4z4Lh --no-check-certificate

注意

您将需要使用--no-check-certificate标志,因为我已经通过使用链接缩短服务bitly.com来缩短链接,以使您更容易键入。通常,当你使用 wget 下载一个文件时,它只是保存到你当前的目录,但是使用bitly.com重命名链接会导致下载时出现奇怪的行为。这个标志纠正了这个问题。

下载完成后(考虑到它只有 8KB 的下载量,应该不会花很长时间),您需要对它进行重命名,以便可以对它进行扩展。通过键入以下命令重命名下载的文件

mv 1i4z4Lh rpiSht1x-1.2.tar.gz

然后通过键入以下内容展开结果

tar -xvzf rpiSht1x-1.2.tar.gz

然后cd进入结果目录(cd rpiSht1x-1.2)并运行

sudo python setup.py install

您现在可以使用这个库了,所以让我们试一试。在您的 SHT15 仍然按照前面的定义连接的情况下,键入以下代码:

from sht1x.Sht1x import Sht1x as SHT1x
dataPin = 11
clkPin = 7
sht1x = SHT1x(dataPin, clkPin, SHT1x.GPIO_BOARD)

temperature = sht1x.read_temperature_C()
humidity = sht1x.read_humidity()
dewPoint = sht1x.calculate_dew_point(temperature, humidity)

temperature = temperature * 9 / 5 + 32     #use this if you'd like your temp in degrees F
print ("Temperature: {} Humidity: {} Dew Point: {}".format(temperature, humidity, dewPoint))

将该代码保存为sht.py并用sudo python sht.py运行。该脚本使用 Adafruit 脚本中定义的函数— read_temperature_C()read_humidity()calculate_dew_point()—从传感器获取电流值,我们将传感器连接到第 7 和第 11 针。然后,它为我们这些不使用公制的人执行快速转换并显示结果。

你应该了解一下你目前的状况:

Temperature: 72.824 Humidity: 24.282517922 Dew Point: 1.22106391724

如您所见,这是一个非常简单明了的库。这些库中的许多都是为 Arduino 编写的,以便与它们通信,谢天谢地,它们已经被移植到 Pi 上运行了。(参见前面关于使用现有代码的旁注。)

连接气压计

也许气象站最有趣的部分之一是 BMP180 气压计芯片,因为变化的气压是天气下一步要做什么的最佳指标之一。一般来说,气压下降表明暴风雨即将来临,气压上升表明前方天气良好。当然,这过于简单化了。

BMP180 芯片运行在 I2C 协议上,所以你必须把它连接到你的 Pi 的 SDA 和 SCL 引脚(引脚#3 和#5 ),就像你连接指南针一样。将接头焊接到电路板后,将 VCC 和 GND 分别连接到引脚 1 和 6,然后将 SDA 和 SCL 分别连接到引脚 3 和 5。

注意

你将芯片的电源连接到 Pi 的 3.3V,而不是 5V。您希望芯片运行在 3.3V 逻辑上,这样它就没有机会损坏 Pi 的精密 3.3V 输入。

为了确保一切连接正确,运行sudo i2cdetect -y 1并确保设备出现。它应该显示为地址0x77,如图 6-10 所示。

img/323064_2_En_6_Fig10_HTML.jpg

图 6-10

i2cdetect 显示正在使用的 0x77 和 0x1e 地址

注意

图 6-10 截图中的 0x1e 设备就是我们正在使用的连接指南针。

同样,这个设备需要一些外部库才能工作。在这种情况下,我们将使用 Adafruit 优秀的BMP085库。

注意

BMP180 芯片的原始版本是 BMP085。尽管后来被替换,但芯片的原理图和引脚排列是相同的,因此所有为 BMP085 编写的库也适用于 BMP180。

要获取必要的库,在您的终端中键入

wget http://bit.ly/NJZOTr --no-check-certificate

正如我们之前所做的,我们需要重命名下载的文件,以便我们可以使用它。在这种情况下,我们下载的文件被命名为NJZOTr。通过键入以下内容来重命名它

mv NJZOTr Adafruit_BMP085.py

这里不需要安装任何东西,所以我们可以直接使用这个库与芯片通信。在同一目录下的新 Python 脚本中,输入以下内容:

from Adafruit_BMP085 import BMP085

bmp = BMP085(0x77)    #you may recognize the I2C address here!

temp = bmp.readTemperature()
temp = temp*9/5 + 32     #if you're not in one of the 99% of countries using Celsius
pressure = bmp.readPressure()
altitude = bmp.readAltitude()

print "Temperature:    %.2f F" % temp
print "Pressure:       %.2f hPa" %(pressure / 100.0)
print "Altitude:       %.2f" %altitude

正如温度传感器的脚本所做的那样,这一小段代码使用预先编写的库及其函数从气压计芯片中读取必要的值。当您运行它时,您应该得到类似图 6-11 的东西。

img/323064_2_En_6_Fig11_HTML.jpg

图 6-11

BMP180 压力传感器的输出

你现在可以从你所有的传感器上读取数据,所以是时候把所有的东西放在一起了!

连接比特

建设这个气象站的一个重要部分是将所有东西(或至少是指南针)放在一个旋转平台上,这样你就可以确定风的方向。正如你在图 6-12 中看到的,我把我所有的芯片都放在一个试验板上,并把它连接到 Pi,这样我就可以更容易地把所有东西(Pi 和试验板)安装到一个旋转平台上。在你懒惰的 Suzan 轴承上有一个相当大的平台,这应该不是问题。

img/323064_2_En_6_Fig12_HTML.jpg

图 6-12

面包片

查看图 6-12 ,您可能会注意到我是如何布线的:我在电路板的一侧使用电源轨进行正极(+)和负极(–)连接,而在另一侧使用电源轨进行 I2C 连接的数据线(SDA)和时钟线(SCL)。这是我发现的将几个不同的 I2C 设备连接到 Pi 的最简单的方法,因为它们共享时钟和数据线。图 6-13 显示了一个更好的布线视图,以防你忘记什么连接到什么。

img/323064_2_En_6_Fig13_HTML.jpg

图 6-13

接线图

将风速计安装到气象站基座后,您现在可以连接 Pi 和电路板罗盘、温度传感器和气压计芯片。由于旋转编码器的引线较短,您可能需要在风速计桅杆上安装一个额外的试验板,如图 6-14 所示。您完成的装配可能看起来如图所示。给你的 Pi 加电,你就可以接收天气预报了。

img/323064_2_En_6_Fig14_HTML.jpg

图 6-14

竣工气象站

我们将编写代码,以便 Pi 每 30 秒查询一次每个传感器,并将结果显示在屏幕上。请参见下一节中的最终代码。

最终代码

Apress.com开始,最终代码为weather.py

import os
import time
from sht1x.Sht1x import Sht1x as SHT1x
import Rpi.GPIO as GPIO
from Adafruit_BMP085 import BMP085
import smbus
import math

GPIO.setmode(GPIO.BOARD)
GPIO.setup(8, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

bus = smbus.SMBus(0)
address = 0x1e

def read_byte(adr):
    return bus.read_byte_data(address,adr)

def read_word(adr):
    high = bus.read_byte_data(address, adr)
    low = bus.read_byte_data(address, adr)

    val = (high << 8) + low
    return val

def read_word_2c(adr):
    val = read_word(adr)
    if (val >= 0x8000):
        return -((65535 - val) + 1)
    else:
        return val

def write_byte(adr, value):
    bus.write_byte_data(address, adr, value)

def checkTemp():
    dataPin = 11
    clkPin = 7
    sht1x = SHT1x(dataPin, clkPin, SHT1x.GPIO_BOARD)
    temp = sht1x.read_temperature_C()
    temp = temp*9/5 + 32        #if you want degrees F
    return temp

def checkHumidity():

    dataPin = 11
    clkPin = 7
    sht1x = SHT1x(dataPin, clkPin, SHT1x.GPIO_BOARD)
    humidity = sht1x.read_humidity()
    return humidity

def checkBarometer():
    bmp = BMP085(0x77)
    pressure = bmp.readPressure()
    pressure = pressure/100.0
    return pressure

def checkWindSpeed()
    prev_input = 0
    total = 0
    totalSpeed = 0
    current = time.time()
    for i in range(0, 900):
        input = GPIO.input(8)
        if ((not prev_input) and input):
            total = total + 1
        prev_input = input
        if total == 90:
            rps = (1/ (time.time()-current))
            speed = math.exp((rps + 0.95)/4.3)
            totalSpeed = totalSpeed + speed
            total = 0
            current = time.time()
    speed = totalSpeed / 10    #average speed out of ten turns
    return speed

def checkWindDirection()
    write_byte(0, 0b01110000)
    write_byte(0, 0b00100000)
    write_byte(0, 0b00000000)
    scale = 0.92
    x_offset = 106        #use the offsets you computed
    yoffset = -175        #use the offsets you computed
    x_out = (read_word_2c(3) - x_offset) * scale
    y_out = (read_word_2c(7) - y_offset) * scale
    direction = math.atan2(y_out, x_out)
    if (direction < 0):
        direction += 2 * math.pi
        direction = math.degrees(direction)
    return direction

# Main program loop
while True:
    temp = checkTemp()
    humidity = checkHumidity()
    pressure = checkBarometer()
    speed = checkWindSpeed()
    direction = checkWindDirection()

    os.system("clear")
    print "Current Conditions"
    print "----------------------------------------"
    print "Temperature:", str(temp)
    print "Humidity:", str(humidity)
    print "Pressure:", str(pressure)
    print "Wind Speed:", str(speed)
    print "Wind Direction:", str(direction)

    time.sleep(30)

摘要

在本章中,您从头开始建立了一个气象站,并安装了必要的传感器来跟踪天气情况,包括气压、温度、湿度、风速,甚至风向。您已经了解了更多关于 I2C 接口的知识,现在应该很好地掌握了如何使用 Python 函数在给定的时间间隔内重复任务。你也做了很多捏造;现在你可以休息一下了,因为下一个项目,媒体服务器,不需要任何构造!

七、媒体服务器

媒体服务器背后的概念是将您所有的媒体文件(音乐和电影)存储在一个中心位置,然后从该位置流式传输到您家中选择的任何设备。如今,几乎每个媒体设备(和一些非媒体设备)都可以连接到网络——如果不是互联网,至少是你的家庭网络。这意味着所有这些机器,也许除了冰箱之外,都可以成为客户端,来自中央服务器的流媒体文件。这是标准的网络语言;存储文件的计算机——无论是媒体文件、电子表格还是网页——被称为服务器,请求这些文件的计算机被称为客户端

碰巧的是,Pi 非常适合充当服务器。这是因为需要的计算能力非常少(Arduino 实际上也可以是媒体服务器,它的功能比 Pi 弱几千倍),存储空间也不是问题,因为你可以从任何连接的存储设备(如外部硬盘驱动器)传输媒体文件。Pi 可以将文件流式传输到任何兼容设备。“可这是 Linux 盒子啊!”我听到你们中的一些人在后排尖叫。“我需要流式传输到我的 Windows 笔记本电脑!”没问题——我们将用作服务器的软件允许 Linux 服务器和 Windows 客户机很好地一起工作。此外,在我向您介绍了将 Pi 用作裸机服务器的过程之后,我将向您介绍一个名为 Kodi 的售后市场媒体服务器解决方案。

关于你的媒体文件,我会假设你是一个正直、守法的公民,为他们所有的电影和音乐付费,并以正确、合法的方式积累了相当多的收藏。正确没错。让我们从你需要的零件开始。

零件购物清单

这个项目几乎不需要零件。所有你需要的是你的 Pi 和一个足够大的外部 USB 硬盘来存储你所有的文件。Pi 应该能够识别大多数现代的外部驱动器,但是我建议,如果您为这个项目购买了一个驱动器,那么您应该将它插入 Pi,并确保在您开始将千兆字节的文件传输到它之前一切正常。

使用 NTFS 驱动器

您使用的 USB 硬盘需要格式化为 NTFS(新技术文件系统)驱动器。NTFS 是一种 Windows 格式,通常需要一些特殊处理才能与 Linux 兼容。在 NTFS 之前,FAT32 是最常用的格式,Linux 和 Unix 对其读写没有问题,但 FAT32 不能处理 4GB 以上的文件,一个高清电影文件很容易超过这个限制。因此,我们转向了 NTFS 格式,这种格式可以轻松处理高达 16TB 的文件。FAT32 也有总驱动器大小的问题;根据文件簇的大小,它最多只能格式化大约 127GB 的驱动器。另一方面,NTFS 格式在 64KB 的集群中有一个 256TB 的理论上限,显然要大得多,并且更适用于当今更大的文件和驱动器大小。

对于第一次设置文件/媒体服务器的许多用户来说,文件大小是一个常见的混淆来源。表格 7-1 将帮助你理解它们。

表 7-1

常见文件大小

|

文件类型

|

文件类型

|

平均尺寸

| | --- | --- | --- | | 歌曲 | mp3 | 5MB | | 音乐视频 | mp4,avi,mpg | 150MB | | 标准清晰度电影 | mp4,avi,mpg | 750MB | | 高清电影 | mp4,avi,mkv,mpg | 大于 1.5GB |

当你看着你当前的音乐和视频收藏,并四处寻找存储它们的驱动器时,请记住这些大小。还要记住:1024KB 等于 1MB,1024MB 等于 1GB,1024GB 等于 1TB。(可以,大多数情况下可以四舍五入到 1000;这是一个二元的东西:2 10 = 1024。)幸运的是,存储价格正在稳步下降,你很可能以不到 150 美元的价格买到 2TB 的硬盘。

因为购买的大多数驱动器都预先格式化为 NTFS 格式,所以让我们通过安装一个名为 NTFS-3g 的程序来确保您的 Pi 可以读写它。打开终端并通过键入以下命令进行安装

sudo apt-get install ntfs-3g

NTFS-3g 是一个开源的读写 NTFS 驱动程序,适用于 Linux、Android、Mac OSX 和其他各种系统。它预装在大多数 Linux 系统上,但是 Pi 没有(在撰写本文时),这就是为什么您需要添加它。

一旦安装了 NTFS-3g,将您的驱动器插入您的 Pi。你可能会看到一个弹出窗口,询问你该怎么做;只需选择“在文件管理器中打开”并继续。一旦您知道您可以读取它(通过查看它的文件),请确保您可以通过打开一个终端并创建一个目录(仅用于测试)来写入它,如下所示:

cd ../../
cd media
ls

cd "My Book"(或您的驱动器的名称,使用ls查找名称)

mkdir test

如果测试文件夹出现,您可以继续。如果没有,请确保您安装了 NTFS-3g,并在必要时重新启动 Pi。

您可能已经注意到,在前面的命令中,“我的书”是用引号括起来的。这是因为虽然文件名可以包含空格,但是当你使用命令行时,你需要考虑空格。如果您需要将目录(cd)更改为一个名为My Book的文件夹,只需输入下面一行就会出现文件未找到错误,因为操作系统会查找一个名为 My 的文件夹,然后停止查找:

cd My Book

解决文件名中空格的方法是在文件名周围使用引号,或者用反斜杠对空格进行转义,如下所示:

cd My\ Book

我们需要在 Pi 的/media目录下创建一个Media文件夹;这是我们将存储所有音乐和电影文件的位置。我们可以稍后在那里创建子目录,但是现在我们只想确保每次我们启动 Pi 时,外部驱动器都会挂载到同一个文件夹中。这是因为当我们设置其他设备(客户端)时,它们将会查找该文件夹,我们不希望每次启动 Pi 时都必须重新配置它们来请求不同的文件夹。要创建该文件夹,请以 root 用户身份进行操作:

sudo mkdir /media/Media

要将Media文件夹设置为挂载点,我们需要编辑一个名为fstab的文件,并插入我们的驱动器信息。首先,我们需要司机信息。在您的终端中,输入以下命令:

sudo blkid

这将列出当前连接到您的 Pi 的所有虚拟和物理驱动器。例如,我的blkid的结果看起来像图 7-1 所示的屏幕。

img/323064_2_En_7_Fig1_HTML.jpg

图 7-1

blkid结果

如您所见,挂载为/dev/sda1: "My Book"的磁盘是我们感兴趣的,我们需要的是该磁盘的 UUID(通用唯一标识符)。

现在,我们通过键入以下命令打开fstab文件

sudo nano /etc/fstab

文件中可能已经有几行了。它们遵循以下格式:

Device name | Mount point | File system | Options | Dump options | File system check options

我们需要使用正确的文件系统和选项将外部驱动器和挂载点添加到文件中。因此,作为一个示例,对于一个虚构的 NTFS 格式的驱动器,我将添加以下内容(这里的每组文本由一个制表符分隔):

UUID=39E4-56YT    /media/Media    ntfs-3g    auto,user,rw,exec    0    0

第一项是您的驱动器的 UUID,第二项是我们之前创建的文件夹(将成为挂载点),第三项是卷类型,最后三项是必要的权限和默认选项。

一旦您添加并保存了您的fstab文件,通过输入

sudo mount -a

(这将强制挂载fstab中列出的所有驱动器,如果它们还没有挂载的话),您应该会听到您的外部驱动器开始旋转。然后,通过键入以下命令(列出所有当前装载的驱动器),查看它是否正确装载到正确的文件夹中:

df -h

如果一切正常,您就可以进入下一步了——安装 Samba。如果您的驱动器没有正确显示,请仔细检查您的fstab文件,因为 UUID 或文本格式中的错误将导致自动挂载失败。这是一个挑剔的文件。

安装 Samba

正如 Samba 的网站所解释的,“Samba 运行在 Unix 平台上,但像本地人一样与 Windows 客户端对话。它允许 Unix 系统进入 Windows 的“网上邻居”而不会引起骚动。Windows 用户可以愉快地访问文件和打印服务,而不知道或不关心这些服务是由 Unix 主机提供的。”名称 Samba 来自 SMB(服务器消息块)协议,它是 CIFS(公共互联网文件系统)的一部分,由微软推出,试图与其他操作系统和睦相处,而不引起彻底的叛乱。

那么,这个程序就是我们需要安装在 Pi 上的,这样你的 Windows 机器集合也可以接收媒体文件,就像你的 Mac 和 Linux 机器集合一样。它预装在许多 Linux 发行版上;然而,圆周率不在其中。安装它就像打字一样简单

sudo apt-get install samba

桑巴作为联络员

曾几何时,计算机可以很好地一起工作。网络并不复杂,计算机可以很容易地通过电话线进行通信,波特率低,信息小。如果你需要和另一台电脑通话,很可能是通过 BBS(公告板系统),这和你使用的是什么操作系统无关。如果你没有使用 BBS,很可能你运行的是 DOS 操作系统,你与之对话的计算机也是如此。那是一段简单的时光。然后,随着计算机变得越来越复杂,不同的操作系统出现了。隔离墙的一边是 Unix 帝国,包括较小的 Linux、Mac 和 BSD 王国。在墙的另一边是伟大的微软帝国,从伟大的国王 DOS 开始,然后是他的继承人,从 Windows 1.01 到今天的 Windows 10 的 Windows 模型。

王国之间存在相对的和平;事实上,双方很少说话,所以没有敌意。然而,随着因特网和其他互联网络的发展,双方必须顺畅无误地交换文件。Unix empire 是两者中较小的一个,它调整了所有的操作系统,很容易成为 Windows 服务器的客户机,因为这是网络设置中的常见配置。然而,Windows 方面拒绝相信它会屈尊从 Unix 服务器接收文件,并且没有做任何事情来使这变得容易——甚至可能。

然而,尽管 Windows 桌面客户机的数量增加了,Unix 和 Linux 服务器却激增了,因此 Windows 客户机最终有必要与 Unix 风格的服务器通信和交换文件。虽然可以做到,但并不容易,通常需要一个精通网络协议和语言的超级用户。进入 Samba——一个旨在让这些不同的计算机轻松通信的程序,减少了用户的麻烦。

配置 Samba

一旦安装了 Samba,我们需要配置它。在编辑之前备份当前的配置文件是一个好主意,这样如果你真的把它弄糟了,你可以恢复它。

为此,使用 Linux 的cp命令:

sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.orig

该命令将smb.conf文件复制到同一目录下的smb.conf.orig文件中。它需要以sudo的身份运行,因为/etc文件夹只能由根用户编辑。完成后,您可以通过键入以下命令打开文件进行编辑

sudo nano /etc/samba/smb.conf

迎接您的将是一个相当大的配置文件——不要让它吓到您。我们只需要改变一些设置。配置文件的大小表明了 Samba 的适应性有多强;因为它在互联网上被广泛使用,既作为网络服务器又作为文件服务器,所以用户可以根据自己独特的需求对它进行修改是很重要的。我们的需求实际上相当简单,因此不需要我们改变程序的默认设置。

我们可能需要编辑的第一个设置是工作组。工作组只是 Samba 服务器(您的 Pi)所属的域。作为家庭媒体服务器,域就是 Windows 所说的“工作组”——你的家庭网络。在全局设置下,更改

workgroup = WORKGROUP

到您的本地工作组的名称,如果您有一个。如果没有设置,请保留工作组设置。

然后,取消对以下行的注释(通过删除哈希符号):

#wins support = no

并将其改为

wins support = yes

在网络部分下,将interfaces行改为

;    interfaces = eth0 wlan0 lo

让下面一行保持原样:

bind interfaces only = yes

最后要更改的部分是共享定义部分。在这个部分中,您可以列出并配置希望 Samba 与其他人共享的字段和文件夹。向下滚动到该部分的底部,并添加以下几行:

 [Media]
     comment = Media Drive
     path = /media/Media
     browseable = yes
     guest ok = yes
     writeable = yes
     public = yes
     available = yes
     create mask = 0666
     directory mask = 0777

这将创建 Samba 安装的共享部分,以匹配前面创建的驱动器和文件夹。它还使文件夹可浏览,并为该文件夹创建正确的共享权限。

设置 Linux 权限

Linux 文件权限是一个有趣的东西,它们可能需要一些启发,因为当你在 Linux 的土地上穿越 Raspberry Pi country 时,你一定会以某种方式遇到它们。每个文件或文件夹都有三个相关联的权限组:所有者*、所有用户组。那些权限或者是 rw 或者是 x ,用于执行。*

当您使用以下命令列出目录中的文件时

ls -l

您可以看到目录中的每个项目前面都有一行,如下所示:

-rwxrwxrwx

或者

drwsr-xr-x

第一个字符要么是一个(连字符)要么是一个d,这告诉你它要么是一个文件,要么是一个目录。接下来,按照所有者、组和所有用户的顺序,将权限分成三组列出。列为-rwxrwxrwx的文件意味着该文件的所有者、属于被分配到该目录的组的用户以及所有用户都拥有该文件的读、写和执行权限。另一方面,如果文件被列为-rwxr-xr-x,则意味着只有所有者拥有写权限(可以对其进行写和保存)。另外两个组可能只读取和执行文件。

如果您需要更改文件的权限,您可以使用chmod命令,这可以显式完成,也可以用这些权限的二进制表示来完成。如果你想明确地做到这一点,每个组使用的标志是u(所有者)、g(组)和o(所有用户)。)例如,如果您想将-rwxrwxrwx文件改为对所有用户只读,您可以输入

chmod o-wx filename

这会将其目录列表更改为-rwxrwxr--。相反,您应该输入

chmod o+wx filename

恢复所有用户的写入和执行权限。

如果您喜欢使用二进制权限,也可以这样做。基本上,每个权限都有一个值;r = 4,w = 2,x = 1。您为每个组的权限添加整数,并以这种方式设置它们。因此,一个组的rwx权限是 7,r-x权限是 5。如果那样做,您需要设置每个组的权限;如果一个文件当前有-rwxrwxrwx权限,并且您想取消该组和所有用户的写权限,您可以输入

chmod 755 filename

这可能有点令人困惑,但是一旦你对权限有了一点了解,一切都会变得很有意义。在我们的 Samba 配置文件中,您已经将掩码和目录权限分别设置为-r-xr-xr-x-rwxrwxrwx,这是我们将该目录中的所有文件传输到客户端所需的。

重新启动 Samba 服务

编辑完配置文件后,通过键入以下命令重新启动 Samba 服务

sudo service smbd restart

当它再次启动并运行时,转到家庭网络中的 Windows 计算机,打开命令提示符。在提示符下,键入

net view \\192.168.xx.xxx

(显然,替换您的 Pi 的 IP 地址)。你应该得到一些东西,如图 7-2 所示。

img/323064_2_En_7_Fig2_HTML.jpg

图 7-2

工作中的 Samba 共享的. Net 视图

不幸的是,作为共享(网络)驱动器连接到您的 Samba 共享在每个版本的 Windows 中都略有不同。因为这本书是关于 Pi 的,而不是 Windows,所以我不能用图形界面来描述每个版本的所有细节。但是,如果您不介意使用命令行界面,在同一个域中位于 192.168.2.3 的 Samba 共享上挂载Media文件夹的命令实际上非常简单。它看起来像这样

net use z: \\192.168.2.3\Media * /USER:pi /P:Yes

如果 Pi 上的一切都设置正确,您应该看到您的Media文件夹被挂载为 Z:驱动器。然而,Windows 7 因不想很好地处理 Samba 共享文件夹而臭名昭著(尽管许多错误似乎已经在 Windows 10 中得到修复)。如果您确信已经正确配置了所有内容,但是仍然看不到文件夹的内容(例如,您得到一个拒绝访问错误),请在不同的操作系统上尝试。您的 Windows 操作系统可能是问题所在。

与 Linux/OS X 连接

“但是等等!”我能听到你们中的一些人在房间后面微弱地尖叫。"如果我们想用 Linux 或 Mac 机器连接到我们的服务器,该怎么办?"

首先,如果您在家里的其他地方运行 Linux 机器,您可能不需要任何帮助来连接 Samba 共享。然而,如果你用的是 Mac,连接起来还是很容易的。在 Finder 中,点按“前往”,然后点按“连接到服务器”(见图 7-3 。)

img/323064_2_En_7_Fig3_HTML.jpg

图 7-3

连接菜单(Mac OS 10.13.4)

在弹出的窗口中,输入地址和共享文件夹,然后单击“连接”(见图 7-4 。)在下一个窗口中输入用于登录 Pi 的名称和密码,该文件夹将作为共享驱动器挂载,可从任何 Finder 窗口访问。如果您碰巧使用 Mac 的 Mavericks (OS 10.9)或更高版本,您也可能无法以“pi”身份连接,但您可能可以以“Guest”身份连接这是 OS X 后期版本的一个问题,不幸的是,我在这里无法轻易解决。

img/323064_2_En_7_Fig4_HTML.jpg

图 7-4

Mac 上的“连接到服务器”对话框窗口

现在,您有了一个可以使用的 Samba 安装,您可以使用它来共享您放入该文件夹的任何内容,并且由于您给予它的权限,您不必担心意外地从网络上的另一个设备删除了您的Media文件夹中的文件。增加或减少共享文件夹的唯一方法是从 Pi 本身——为您的音乐和电影提供一点安全保护。

Kodi 和 Plex

如果所有这些对您来说似乎有点太复杂,那么在将您的 Pi 用作媒体服务器时,还有一些其他的选择。在我看来,最受欢迎和最有效的两种解决方案是 Kodi 和 Plex。

让我们从 Plex 服务器开始,它对 Pi 来说相对较新。您需要从安装 HTTPS 传输包开始,它安装在 Raspbian 的某些版本上,但在其他版本上没有。在终端中,输入

sudo apt-get install apt-transport-https

要么安装它,要么听消息告诉你,你有它。

接下来,您需要将dev2day存储库添加到您的存储库列表中。为此,您需要一个用于回购的密钥。在终端中,输入

wget -O – https://dev2day.de/pms/dev2day-pms.gpg.key | sudo apt-key add –

一旦您安装了密钥,您就可以使用

echo "deb https://dev2day.de/pms/ jessie main" | sudo tee /etc/apt/sources.list.d/pms.list

然后更新你的回购清单

sudo apt-get update

现在,您可以下载 Plex 服务器

sudo apt-get install -t jessie plexmediaserver

安装完成后,您需要编辑配置文档,以便允许服务器使用用户“pi”(我们的普通用户/登录名)运行。使用打开文档进行编辑

sudo nano /etc/default/plexmediaserver.prev

并将最后一行改为

PLEX_MEDIA_SERVER_USER=pi

保存您的更改,然后使用重新启动服务器

sudo service plexmediaserver restart

最后,使用服务器的图形界面向服务器添加一些文件。打开 web 浏览器,在地址栏中键入您的 Pi 的 IP 地址,然后键入:32400/web/。换句话说,如果你的 Pi 的地址是 192.168.2.3,就在地址栏里打上192.168.2.3:32400/web/,回车。

系统会提示您登录您的 Plex 帐户(如果您还没有,可能需要创建一个),然后按照提示将媒体文件夹添加到您的 Plex 服务器。添加文件非常容易,即使它们在外部硬盘上(这可能是你最终要做的)。图 7-5 显示了将库文件夹添加到服务器的过程中的一个步骤;添加后,Plex 将扫描文件,下载必要的信息(如电影海报图标、演员信息等),然后将其提供给客户端设备。

img/323064_2_En_7_Fig5_HTML.jpg

图 7-5

添加丛媒体库

你只需要一个客户端设备就可以访问你的媒体文件,比如智能手机、平板电脑、智能电视,甚至是 Kindle Fire 或 Firestick。

另一个选择是安装 Kodi。Kodi 过去被称为 XBMC (Xbox Media Center),但后来被更新了。它通常用于 Linux 服务器,如 Pi 和 Android TV 设置。Kodi 运行在几个不同的操作系统之上,但我碰巧更喜欢 OpenELEC,它代表开放嵌入式 Linux 娱乐中心。

要安装 OpenELEC,您需要将它放在一个新的 SD 卡上,这样您就不会破坏所有 Pi 项目使用的 Raspbian 副本。在你的桌面机器上,进入 http://openelec.tv/downloads ,展开树莓派部分。为您的 Pi 型号选择正确的版本,并按照他们的说明将映像安装到您的 SD 卡上。

一旦它安装完毕并且您已经启动了您的 Pi,OpenELEC 安装将引导您完成连接到互联网(图 7-6 )、配置 SSH 和 Samba 以及设置您的媒体库的过程。它比 Plex 安装稍微复杂一点,但也更容易配置,所以很容易得到您想要的样子。

img/323064_2_En_7_Fig6_HTML.jpg

图 7-6

设置 OpenELEC

Python 在哪里?

但是等等!这一章的 Python 在哪里?嗯,这一章没有 Python。这是一个不需要编程的情况的好例子;现有的工具已经足够好了,有时候知道什么时候编程就像知道什么时候编程一样有价值。

摘要

在这一章中,您学习了一些关于服务器和客户端如何在互联网和家庭网络上运行的知识。您了解了如何让 Pi 和其他计算机(特别是 Windows)一起正常运行,以及如何使用三种不同的免费文件共享程序在家庭网络中共享所有媒体文件,通过任何连接的设备都可以访问这些文件。

在下一章中,您将学习如何使用 Pi 来保护您的家庭网络——不是来自黑客,而是来自物理入侵者。

八、家庭安全系统

生活在现代可以。。。好吧,让我们面对它:这可能是一件可怕的,有压力的事情。坏人和他们犯下的罪行无处不在。根据联邦调查局犯罪统计网站的数据,2016 年,美国发生了大约 790 万起财产犯罪,这是有统计数据的最近一年。虽然财产犯罪率在过去的 14 年里稳步下降,但住在和平街道上的日子已经一去不复返了,在那里邻居互相认识,上班时不锁门。

幸运的是,我们能够保护我们的家园,也能够用摄像机(静态和视频)监视这些家园,这些摄像机安装在我们需要的地方,并能够将视频实时传输到我们任何始终连接的设备,如我们的笔记本电脑或智能手机。我们可以在我们的房子里安装传感器,比如运动传感器和行程开关,并使用从这些传感器收集的信息作为触发器来执行特定的操作。如果你愿意花这笔钱,你可以安装各种系统,从保护你的房子免受火灾和盗窃,到提醒你一氧化碳(CO)泄漏。

碰巧的是,树莓 Pi 非常适合做所有这些事情,而且比整个闭路摄像机网络和运行它们的计算机系统便宜得多。不需要太多的计算能力——它足够小,足够省电,可以实际安装在现场,它可以通过可用的相机(甚至红外相机)拍摄重要时刻的照片!),并且因为它连接到家庭网络,所以它可以在出现问题时提醒您。太好了。

是的,你可以养一只看门狗。其实很多人(有人会说正常人)都是这么做的。但是让我们花点时间考虑一下养狗和养树莓派的利弊。然后,我们可以开始用树莓 Pi 构建我们的家庭安全系统。

狗是安全的

众所周知,狗是人类最好的朋友,近一万年来,它们一直被用作看门狗。它们是狼的后代,有各种形状和大小,从一品脱大小的吉娃娃到巨大的大丹狗。

长期以来,狗的工作之一就是保护家不受入侵者的侵害。他们非常忠诚,保护他们的人类家庭成员和他们的“巢穴”,会对入侵者吠叫,甚至攻击。为了保持这种行为,它们需要食物——有时相当多。虽然它们通常很可爱,令人想抱抱,并且在寒冷的冬夜能很好地让你的脚保持温暖,但不幸的是,它们不得不吃东西的事实意味着它们也不得不消灭——这对所有相关的人来说都是一项恶臭的任务。

狗也是没有能力升级的。上次我试图给我的狗插 USB 线时,它尖叫着跑向我妻子。虽然当你在路上开车时,狗把头伸出窗外时会非常可爱,但你不能升级它们的驱动程序或使用软件包管理器来下载更有效的气体消除程序。

结果如何?狗很适合看家,但是它们有一些严重的缺点。

作为证券的树莓派

树莓派( Rubus strigosus Pi )通常被认为是机器人爱好者最好的朋友,至少在整整六年的时间里,它一直被用来制作各种稀奇古怪的项目。这些设备是从 20 世纪 80 年代早期的 Acorn RISC 机器发展而来的,如前所述,有各种各样的版本:版本 1、版本 2、版本 3、版本 3+、Zero、Zero W。。

树莓派并没有真正的特定工作,但作为一台计算机,众所周知,它会遵循所有给它的指令。如果你给它编程,让它找出 1 到 10000 之间的所有质数,它就会这么做;另一方面,如果你告诉它继续寻找质数,直到一头猪从头顶飞过,它将继续计算,直到它的处理器烧坏,或者直到小猪长出翅膀。要做这些惊人的壮举,Pi 不一定要吃,也不一定要消除。缺乏代谢有机物质的代价是 Pi 不能在寒冷的冬夜让你的脚保持温暖。(其实我收回那句话;当版本 3 执行一些繁重的任务时,比如视频处理,它可能会变得有点热。但还是又小又多刺。)

然而,您可以通过明智地使用sudo apt-get install命令来升级 Pi。Pi 欢迎 USB 输入,它可以通过编程使用传感器来监视您的房子及其周围的地面,并在这些防御遭到破坏时向您发出警报。不幸的是——从经验来说——如果你在街上开车,把头伸出窗外,人们会用非常奇怪的眼神看着你,但没有恶臭气体的问题,所以就这样了。

结果如何?树莓派有一些严重的缺点,但这些都可以克服,让它看着你的房子。既然这个一本关于圆周率的书,那就是我们要用的。

使用传感器网络

家庭安全系统(以及第六章中的气象中心)基于传感器网络的概念。如果计算机像大脑一样,传感器就是允许它从物理世界收集信息并与之交互的感官。相机像眼睛,簧片开关像指尖,压力开关像被笨手笨脚的狗踩过的脚趾。没有传感器,机器人什么都不是,任何需要与物理世界互动的机器人大脑都完全依赖于传感器网络。

事实上,这是 Pi 最酷的地方之一——它能够轻松地与传感器等物理事物交互。大多数现代台式机和笔记本电脑都取消了所有有趣的端口,如并行和串行端口,只剩下几个单独的 USB 端口和一个以太网端口。这使他们变得残疾,如果没有特殊的设备和相应的代码,他们无法轻松地与“真实”世界互动。与此同时,Pi 可以通过其 GPIO 引脚直接插入运动传感器,并通过几行代码让你知道 Slenderman 是否在你卧室后面的灌木丛中爬行。

在我们的安全系统中,我们将使用几个传感器:一个红外运动传感器、一个压力开关、一个磁传感器和一个簧片或限位开关。运动传感器可以放置在场地的任何地方。压力开关放在门口可能会很有用,因为入侵者很可能会踩到门口。磁传感器可用于检测窗户是否被打开,簧片开关可用于确定是否有人触碰绊线。如果任何传感器被触发,我们可以使用 Pi 的机载相机拍照,并随时访问这些照片。最后,如果我们的安全网络中发生了有趣的事情,我们可以使用我们的家庭网络让 Pi 向我们发送文本消息和/或电子邮件消息,就像安全公司在检测到警报时给你打电话一样。

这是我们将要使用的传感器网络。它很基本,但也可以无限扩展。虽然我们将只使用每种传感器中的一种,但如果你想的话,你可以很容易地添加更多的传感器(例如,在你家的每个窗户上安装一个磁性传感器)。

了解下拉电阻

任何时候使用几乎任何电路的输入时,都要知道并记住一个重要概念,即浮动输入和下拉(或上拉)电阻。基本上,每当一个引脚(如 Pi 上的 GPIO 引脚)被设置为从电压源(如传感器)读取输入时,它就被称为浮动输入*,直到该引脚读取到一些电压。在传感器发出电压信号之前,引脚上的电平几乎可以是任何值。这个不确定的浮动电压会严重影响你的程序;如果你已经设置了自毁程序,当引脚读取 2.3V 值时激活,并且浮点值发生达到 2.3V,*嘣!我们需要一种方法,在没有从引脚读取任何内容时,将引脚设置为一个已知值(比如逻辑高或逻辑低)。

解决这个问题的方法是使用一个上拉下拉电阻。该电阻将输入引脚连接到 Vcc 或 GND(分别为上拉或下拉)。这样,如果没有输入,引脚将读取 Vcc 或 0,我们将知道该值。这通常通过物理电阻(通常为 100K 或 100K)来实现,但许多开发板(包括 Pi)将允许您通过软件“虚拟地”实现这一点——当您在有限的空间内工作时,这是一个巨大的优势。使用 GPIO 库,您可以将一个引脚声明为输入,同时像使用下拉电阻一样使用以下语法“下拉它”:

GPIO.setup(11, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

这将在引脚#11 读取的值定义为低,直到它从传感器接收到电压;此时,电压被拉高,程序可以运行。当高值消失时,引脚再次被拉低,直到该过程重复。一个上拉电阻做几乎同样的事情,除了引脚被拉高(到 Vcc)直到一个输入出现。

零件购物清单

为了建立一个有效的家庭安全系统,你需要几个部件:

当然,有些部分是可选的;这完全取决于你希望你的安全系统有多全面。随着安全系统的发展,您甚至可以在列表中添加项目。每个传感器只是增加了您的传感器网络,扩大了您系统的覆盖范围。

无线连接到您的网络

当您将您的 Pi 设置为安全系统的主控制器时,它必须与您的家庭网络连接,以便您可以远程登录和管理它,并向您发送文本消息,通知您有违规行为。当您连接到网络时,您可以选择有线或无线。当然,每种选择都有利弊,但是我强烈建议你使用无线连接。这主要是因为两个原因:无线连接允许您将 Pi 放置在任何地方,而不必将以太网电缆铺设到其位置,并且无线连接也更安全——窃贼可以切断您的 Pi 的有线连接,使其无法使用,但无线连接则不是这样。

您需要做的主要事情是设置您的 Pi 拥有一个静态 IP 地址。这将允许您从任何地方远程登录到您的 Pi,而不管自您上次远程登录以来它是否已关机。如果您让 Pi 从您的家庭路由器动态接收其 IP 地址,那么如果 Pi 必须重新启动,IP 地址可能会改变,这意味着您将无法登录(因为您不知道新地址是什么)。

幸运的是,为您的 Pi 无线连接设置一个静态 IP 并不困难,尽管这可能会令人困惑,因为每次发布新的 Pi 模型时,方法似乎都会发生变化。您需要知道路由器的地址,通常是类似于192.168.0.1的地址。

在 Pi 的终端提示符下,输入

sudo nano /etc/dhcpcd.conf

并滚动到文件的底部。您可以在这里输入特定网络和特定地址的信息。假设您的路由器有一个地址192.168.0.1,您希望您的 Pi 有一个地址192.168.0.4。在/etc/dhcpcd.conf的底部输入以下内容:

interface wlan0
inform 192.168.0.4
static routers=192.168.0.1
static domain_name_servers=8.8.8.8 8.8.4.4

第一行决定您设置的是有线还是无线接口。第二行给出 Pi 的新静态地址,第三行给出路由器的地址。最后,第四行将 DNS 服务器设置为 Google 的 DNS。

一旦你输入了这些信息,重启你的 Pi。根据您的无线设置,您可能需要使用 Pi 的桌面界面为您的个人网络添加密码。然而,一旦这样做了,你的 Pi 将总是有相同的地址。现在,即使它需要重新启动,您也可以随时登录,使用该地址来管理它。

现在,要使用静态 IP,您需要在您的 Pi 上运行一个 SSH 服务器。根据您第一次设置 Pi 的方式,您可能已经运行了一个 Pi。启动并运行 SSH 服务器最简单的方法是通过键入以下命令来运行您的raspi-config工具

sudo raspi-config

在命令行中。你会看到raspi- config屏幕(图 8-1 )。

img/323064_2_En_8_Fig1_HTML.jpg

图 8-1

raspi-config 工具

光标向下移动到选项#5,接口选项,按右箭头键高亮显示,然后按回车键。将光标向下移动到 P2 SSH,再次突出显示并按回车键。确保在下一个屏幕上高亮显示(图 8-2 )并按下回车键。

img/323064_2_En_8_Fig2_HTML.jpg

图 8-2

启用 SSH 服务器

然后,通过选择<完成>并按回车键退出raspi-config工具。最后,通过键入以下命令重新启动您的 Pi

sudo reboot

在终端中,您的 SSH 服务器应该已经启动并正在运行。现在,您可以从任何地方远程登录到您的 Pi。如果您使用的是 Windows 机器,您需要下载免费工具 PuTTY 来登录您的 Pi。如果您使用的是 Mac 或 Linux 系统,ssh 已经启用了。只需使用以下命令:

ssh -l pi <your pi's IP address>

在密码提示处输入raspberry,你就进入了!使用 PuTTY,在框中输入您的 Pi 的 IP 地址,添加用户名和密码,然后单击“连接”现在,您可以从任何地方通过命令行管理您的 Pi。

访问 GPIO 引脚

如前所述,如果您已经阅读了本书的其他章节,您已经看到,Pi 的 GPIO 引脚是我们将 Pi 与物理世界(如传感器、伺服系统、电机和灯)相连接的方式。为了做到这一点,我们使用了一个专门为此设计的 Python 库:RPi.GPIO

要使用该库,您可能需要手动安装另外两个库。(这取决于你运行的是哪个版本的 Raspbian。)首先,通过键入以下命令确保您的 Pi 是最新的

sudo apt-get update

然后,通过键入以下命令安装 Python 开发包

sudo apt-get install python-dev

现在,为了访问 pin,您调用

import RPi.GPIO as GPIO

在程序的第一行中输入,然后通过键入

GPIO.setmode(GPIO.BOARD)

这使您可以根据标准引脚排列图上的标签来识别引脚(如图 8-3 所示)。

img/323064_2_En_8_Fig3_HTML.jpg

图 8-3

Pi 的 GPIO 引脚的引脚排列图

注意

请记住,对于GPIO.setmode(GPIO.BOARD),当您提到引脚 11 时,您实际上是指物理引脚#11(在图 8-3 的图中转换为 GPIO 17),不是 GPIO11,它转换为物理引脚#23。

一旦设置好模式,就可以将每个引脚设置为输入或输出。Arduino 的用户可能会认识到这里的概念:

GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.IN)

等等。一旦将一个管脚设置为输出,就可以通过使用

GPIO.output (11, 1)

或者

GPIO.output (11, True)

然后使用以下命令将其关闭

GPIO.output (11, 0)

或者

GPIO.output (11, False)

当您将一个引脚配置为输入时,记得如前所述设置一个上拉或下拉电阻。

设置运动传感器

家庭安全网络最重要的部分之一可能是运动传感器(如图 8-4 所示),但有几点需要注意。你不能只依靠你的运动传感器,因为当你这样做的时候,它会被邻居的猫或者雪人触发(不一定是坏事)。然而,如果你把它和所有其他传感器一起使用,你可能会有好运气。

img/323064_2_En_8_Fig4_HTML.jpg

图 8-4

该运动传感器

我们正在使用的传感器,通过视差或接近克隆,通过检测周围环境中物体发出的红外(热)水平的变化来检测运动。像大多数传感器一样,它通过在其输出引脚上输出“高”或“1”信号来表示已经检测到变化。它有三个引脚:Vcc、Gnd 和输出。

引脚是(从图 8-4 的左起)OUT、+和-。这种特殊传感器的一个很好的特点是它可以使用 3V 到 6V 的任何电压。要使用和测试它,将(–)引脚连接到 Pi 的接地引脚(引脚#6),将(+)引脚连接到 Pi 的 5V 引脚(引脚#2),并将 OUT 引脚连接到其中一个 GPIO 引脚。

为了测试传感器和我们的编码能力,我们将从相应地设置 GPIO 引脚开始。我们可以使用一个简单的设置来测试我们的代码——试验板上的一个 LED,当传感器被触发时,它就会亮起。用nano motion.py启动一个新的 Python 脚本(姑且称之为motion.py)并输入以下内容:

import RPi.GPIO as GPIO
import time

GPIO.setwarnings (False) #eliminates nagging from the library
GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup (13, GPIO.OUT)

while True:
    if GPIO.input (11):
        GPIO.output (13, 1)
    else:
        GPIO.output (13, 0)

测试代码到此为止!要进行测试,首先将传感器上的(+)引脚连接到 Pi 上的#2 引脚。将 OUT 引脚连接到 Pi 上的引脚#11。将(–)引脚连接到试验板上的公共地线。将接地线连接到 Pi 上接地引脚,例如引脚#6。最后,将 Pi 上的 13 号引脚连接到 LED 的正极引脚(通过一个电阻),并将 LED 的负极引脚连接到公共接地线。您最终应该会看到如图 8-5 所示的东西。

img/323064_2_En_8_Fig5_HTML.jpg

图 8-5

测试运动传感器

当您运行前面的脚本时(记住使用sudo,因为您正在访问 GPIO 引脚),当您在传感器周围移动您的手时,LED 应该会亮起,然后在几秒钟不动后,它应该会再次熄灭。如果它不起作用,检查你的连接和你的部件——一个烧坏的 LED 会导致所有的故障诊断头痛,相信我!

让传感器保持原样,因为我们将在我们的系统中使用它,让我们继续讨论簧片开关。

设置簧片开关

簧片或限位开关在许多情况下都是一个有用的工具,尤其是在我们的安全系统中。它经常被机器人用来确定运动的极限,无论是开车撞墙还是关闭物体周围的手爪。它的概念很简单:开关是常开的,没有电压通过,它有一个从开关主体伸出的电枢,像一个长杆。当外部物体按压杠杆时,它会闭合开关,通过电路发送电压——在我们的例子中,发送到 Pi 上监听信号的输入引脚。我们正在使用的限位开关也被称为“超小型快动开关”(参见图 8-6 。)

img/323064_2_En_8_Fig6_HTML.jpg

图 8-6

限位开关

从开关主体伸出的长臂允许远处的物体闭合开关的触点——从开关伸出的小驼峰。它有三个端子,但我们只使用两个,因为我们只对开关何时闭合感兴趣。

在我们的例子中,我们将使用一个限位开关来确定绊网是否被拉动。您可以将开关安装在墙上,并从对面的墙上拉一根细线或钓鱼线到开关的杠杆上。把它放在适当的位置,这样如果有人走进线中,他们就会拉下杠杆,启动开关。

因为我们使用的是物理开关,而不是运动检测器之类的传感器,所以我有必要向您介绍一下去抖的概念。物理开关的一个共同特点是,因为它们通常由弹性金属制成,当它们第一次被激活时,它们往往会弹开一次或多次,通常几乎是以微小的增量弹开,然后才形成稳定的接触。结果是在电压稳定在一个稳定的高或低之前,一个非常快速的开-关-开-关-开-关“震颤”。为了解决这个问题,我们去抖开关,只在它不再来回跳动时读取,就像这样:

import time
prev_input = 0
while True:
#take a reading
input = GPIO.input(11)
#if the last reading was low and this one high, print
if ((not prev_input) and input):
    print("Button pressed")
#update previous input
prev_input = input
#slight pause to debounce
time.sleep(0.05)

这个小脚本很好地阐释了这个概念。如果上一次按钮按下后不到 0.05 秒,它将忽略下一次按钮按下。

因此,为了测试我们的开关,让我们将它连接到一些 GPIO 引脚,并确保当它的状态改变时,我们可以读取输入。只需使用开关,将 Pi 的电源引脚(#2)连接到开关最左边的引脚,如图 8-7 所示。然后,将中间的引脚连接到您的 Pi 的 11 号引脚。尝试以下代码:

import time
import RPi.GPIO as GPIO
GPIO.setwarnings (False)
GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
prev_input = 0
while True:
    input = GPIO.input (11)
    if ((not prev_input) and input):
        print "Button pressed"

    prev_input = input
    time.sleep (0.05)

当你运行这个脚本(记住使用sudo)时,按下开关将直接通过它把电压从引脚#2 发送到引脚#11,因此在引脚#11 处记录为高。这是一个去抖信号,当你按下按钮时,Pi 应该显示“按钮已按下”。恭喜你!当一个开关被按下时,你能阅读!

让我们转到下一个开关。

设置压力开关

压力开关与限位开关非常相似,尽管看起来很不一样(图 8-7 )。)

img/323064_2_En_8_Fig7_HTML.jpg

图 8-7

压力开关

使用的不是物理杠杆和按钮,而是一个方形垫,它只是将压力记录为电压的变化。因此,它比限位开关更容易连接。将您的 Pi 引脚#2 连接到其中一根导线,将另一根导线连接到引脚#11。然后,运行与限位开关相同的脚本,用手指按下面板进行测试。瞧啊。您现在正在从压力开关中读取数值!例如,这非常适合于从迎宾垫下面读出脚步声。

连接磁性传感器

磁性传感器(如图 8-8 所示)是一种小型设备,虽然在某些特定应用之外并不常用,但在像我们这样的应用中却能派上用场。它测量周围的磁场,并在磁场变化时发出信号。因此,它非常擅长确定两块金属的相对位置何时发生了变化。

为了确保我们不会得到任何错误的读数,我们可以使用一些小的外部磁铁来影响传感器;我们正在使用的这个带有两个小钕磁铁,就是为了这个目的。

img/323064_2_En_8_Fig8_HTML.jpg

图 8-8

该磁性传感器

为了测试我们的磁传感器,我们可以再次使用我们一直在使用的switch.py代码。将传感器附带的跳线连接到传感器上的连接器块,然后将它们连接到您的 Pi:红色连接到引脚 2,黑色连接到引脚 6,白色连接到引脚 11。现在,只需将代码改为

import time
import RPi.GPIO as GPIO
GPIO.setwarnings (False)
GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
prev_input = 0
while True:
    input = GPIO.input (11)
    if ((not prev_input) and input):
        print "Field changed"

    prev_input = input
    time.sleep (0.05)

并运行脚本。你的终端将保持空白,直到你挥动磁铁通过传感器。(您可能需要尝试不同的距离和速度。我的经验是磁铁必须靠得很近才能对准。)此时,它会告诉您“字段已更改”经过一点实验,你会知道你应该在哪里安装磁铁,以使磁场读数的变化影响你的安全系统。例如,现在你可以将传感器安装在滑动窗户的一个窗格上,将磁铁安装在另一个窗格上,如果窗户滑动打开,磁性传感器将记录磁铁的移动。

设置私家侦探的摄像头

最后,使 Pi 作为安全系统关键设备具有吸引力的一个特征是它能够从小型内置相机中拍照。虽然这意味着 Pi 必须放置在一个战略位置,以拾取任何有趣的东西,但 Pi 非常小,找到一个好的位置应该不成问题。

要拍照,你必须让两个组件在你的 Pi 上工作:无线和相机。我之前讨论过无线设置;如果您还没有配置相机,也可以使用raspi-config工具来启用它。

一旦你启用了相机,你有两个命令可以使用:raspistill(捕捉图片)和raspivid(捕捉视频)。每个都可以与各种标志和选项一起使用,以更改帧大小、采集速率和其他配置。

然而,我们对拍摄静态照片感兴趣;虽然可以通过实时视频流传输视频,但需要一些额外的软件工具,这些工具可能很难安装。拍照是从命令行对raspistill的简单调用,或者使用picamera Python 库(这就是我们将要使用的)。要进行试验,请在新的 Python 脚本中输入以下内容:

from picamera import PiCamera
camera = PiCamera()
camera.capture('image.jpg')
If you get the message that picamera is unknown, you'll need to install it with
sudo apt-get install python-picamera

并确保已经用您的raspi-config工具启用了它。标记为“image.jpg”的静止图像将存储在当前目录中。我们可以将这个拍照功能放在一个take_pic()函数中,每当传感器被触发时就调用它。我们就有了证据,如果我们需要它来佐证的话!

从 Pi 发送文本消息

在我看来,当不寻常的事情发生时,让你的 Pi 给你发短信是这个项目最酷的部分之一,如果你要出城,这尤其有用。来自 Pi 的通知可以让你知道你需要打电话给你的邻居(或警察),让他们检查你的房子。这真的很简单:Pi 使用本地网络发送电子邮件消息,然后由您的移动运营商翻译成 SMS 或文本消息。

你需要一个可以上网的电子邮件帐户;例如,我们大多数人都有 Gmail 或雅虎账户。你还需要知道如何用手机运营商通过电子邮件发送短信。每个运营商略有不同,但基本概念是相同的——向某个号码(例如,<mobile_number>@txt.<carrier>.net)发送电子邮件会以文本形式发送。我在& T 使用,如果你给19075551212@txt.att.net发邮件,它会以文本形式发送。请向您的特定运营商咨询要使用的地址和格式;如果这些信息在他们的网站上不容易找到,那就去问技术人员。然后,使用 Python 的smtplib库,你可以发送电子邮件到你的手机。

如果我用下面的代码向您展示可能是最简单的:

def send_text(str):
    HOST = "smtp.gmail.com"
    SUBJECT = "Break-in!"
    TO = "xxxxxxxxxx@txt.att.net"
    FROM = "python@example.com"
    text = str
    BODY = string.join(("From: %s" % FROM, "To: %s" % TO, "Subject: %s" % SUBJECT, "", text), "\r\n")
    s = smtplib.SMTP('smtp.gmail.com',587)
    s.set_debuglevel(1)
    s.ehlo()
    s.starttls()
    s.login("username@gmail.com", "mypassword")
    s.sendmail(FROM, [TO], BODY)
    s.quit()

用一个字符串调用send_text()函数,比如“OMG 我被抢劫了!”会给你发短信。显然,这段代码是为 AT & T 设计的,并且使用了一个 Gmail 账户。您需要根据您的运营商和电子邮件提供商的需要对其进行修改。Gmail 的smtp访问是通过端口 587,正如你在前面代码的第 9 行看到的;这对于雅虎或 MSN 可能有所不同。当您在任何传感器上检测到输入时,您可以调用这个函数,您甚至可以根据哪个传感器被触发来调整发送的字符串。

实现回调

在这个项目中还有一个重要的想法需要探索,那就是回调的概念。你可能已经注意到没有简单的方法来检查每个开关;你必须继续“轮询”每一个开关,并希望在你做其他事情的时候没有不寻常的事情发生。如果你只有三四个开关和传感器,这没什么大不了的,但如果你开始增加网络,事情发生和你得到通知之间的延迟可能会很快变得难以控制;当你检查 16 号磁传感器时,2 号限位开关可能会被触发,而且你在接下来的两秒钟内都不会知道。当然,到那个时候,入侵者可能已经溜过绊网,并且正在打破你所有的盘子或者偷走你的星球大战纪念品的路上。

幸运的是,Python(和 Raspberry Pi)已经解决了这个开关检查问题。它嵌入在RPi.GPIO库中:*线程回调中断。*这允许我们为每个交换机启动不同的程序线程。每个线程都将进入“等待”模式,什么也不做,而程序的其余部分(以及其余的线程)继续处理它们的事务。如果开关被触发,它会立即向主程序发出一个回调或中断,让它知道(“嘿!我在这里被绊倒了!”),然后它执行我们希望它执行的任何功能。这样,我们可以确保不会错过重要的按钮按下或开关跳闸。同时,所有其他线程继续它们的保持模式。在这个模式的底部,一个开关作为基础;如果那个开关被触动,你可以结束程序。否则,它将在一个while循环中继续。

这种回调功能是使用两个函数中的一个来实现的:GPIO.wait_for_edge()GPIO.add_event_detect(). GPIO.wait_for_edge()就是这样做的——等待任何特定引脚的上升或下降沿,然后在检测到该沿时采取行动。另一方面,GPIO.add_event_detect()等待特定管脚的上升沿或下降沿,然后调用其参数中声明的函数。你可以在本章后面的最终代码中看到它们的使用。请注意,对于每个传感器或开关,我们都有一个唯一的回调函数,该函数对于该传感器是唯一的,这样我们就可以准确地知道哪个开关被触发了。

连接所有的位

既然我们已经确定了如何使用这个难题的所有部分,让我们快速地回顾一下如何连接所有的部分。

您需要使用以太网电缆进行所有连接;它很坚固,容易使用,而且(大部分)防水。剥去外壳,接触到里面的电线,除了将每个传感器连接到您的 Pi 所需的两三根电线之外,将所有电线都剪掉。你需要一个小试验板放在 Pi 旁边,因为所有东西都应该共享一个地。

找一个好地方安装你的 Pi,你可以把它插上电源(不用担心电池),安装相机,这样它就可以拍出好的照片。一旦你找到了一个地方,你可以用海报油灰把所有东西固定住。你可能想用一个塑料或金属的项目箱把所有东西放在一起。

最后,为所有传感器找到合适的位置。记住,他们不需要在 Pi 的视线范围内:只要你能把以太网电缆接到他们那里,那就是一个好位置。牢固连接电缆,确保没有人会被绊倒。将所有负极线连接到试验板上的公共接地排,然后将每根正极线连接到一个 GPIO 引脚。在这个阶段,写下哪个传感器连接到哪个引脚可能是个好主意,这样您就可以在代码中引用它。

最终代码

这就完成了这个项目的所有部分。剩下的就是把它们放在你的最终代码中,就像这样(你可以在apress.com下载最终代码文件,名为home_security.py):

import time
import RPi.GPIO as GPIO
from picamera import PiCamera
import string
import smtplib

GPIO.setwarnings (False)
GPIO.setmode (GPIO.BOARD)
time_stamp = time.time() #for debouncing
camera = PiCamera()

#set pins
#pin 11 = motion sensor
GPIO.setup (11, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

#pin 13 = magnetic sensor
GPIO.setup (13, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

#pin 15 = limit switch
GPIO.setup (15, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

#pin 19 = pressure switch
GPIO.setup (19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

def take_pic(sensor):
    camera.capture(sensor + ".jpg")
    time.sleep(0.5) #wait 1/2 second for pic to be taken before continuing

def send_text(details):
    HOST = "smtp.gmail.com"
    SUBJECT = "Break-in!"
    TO = "xxxxxxxxxx@txt.att.net"
    FROM = "python@mydomain.com"
    text = details
    BODY = string.join(("From: %s" % FROM, "To: %s" % TO, "Subject: %s" % SUBJECT, "", text), "\r\n")
    s = smtplib.SMTP('smtp.gmail.com',587)
    s.set_debuglevel(1)
    s.ehlo()
    s.starttls()
    s.login("username@gmail.com", "mypassword")
    s.sendmail(FROM, [TO], BODY)
    s.quit()

def motion_callback(channel):
    global time_stamp
    time_now = time.time()
    if (time_now - time_stamp) >= 0.3: #check for debouncing
        print "Motion detector detected."
        send_text("Motion detector")
        take_pic("motion")
    time_stamp = time_now

def limit_callback(channel):
    global time_stamp
    time_now = time.time()
    if (time_now - time_stamp) >= 0.3: #check for debouncing
        print "Limit switch pressed."
        send_text("Limit switch")
        take_pic("limit")
    time_stamp = time_now

def magnet_callback(channel):
    global time_stamp
    time_now = time.time()
    if (time_now - time_stamp) >= 0.3: #check for debouncing
        print "Magnetic sensor tripped."
        send_text("Magnetic sensor")
        take_pic("magnet")
    time_stamp = time_now

#main body
raw_input("Press enter to start program\n")

GPIO.add_event_detect(11, GPIO.RISING, callback=motion_callback)
GPIO.add_event_detect(13, GPIO.RISING, callback=magnet_callback)
GPIO.add_event_detect(15, GPIO.RISING, callback=limit_callback)

# pressure switch ends the program
# you could easily add a unique callback for the pressure switch
# and add another switch just to turn off the network
try:
    print "Waiting for sensors..."
    GPIO.wait_for_edge(19, GPIO.RISING)
except KeyboardInterrupt:
    GPIO.cleanup()

GPIO.cleanup()

摘要

在本章中,您学习了什么是传感器,传感器网络的概念,以及如何将不同的传感器连接到 Pi 的 GPIO 引脚。您设置了限位开关、压力开关、磁传感器和运动传感器(在下一章中,我们也将在 Pi 供电的猫玩具中使用它们)。利用您在这里获得的知识,您现在有能力创建一个全尺寸的安全系统,其宽度仅受您拥有的传感器数量和将它们连接在一起的电线数量的限制。