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

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

原文:Learn Raspberry Pi Programming with Python

协议:CC BY-NC-SA 4.0

一、树莓派简介

所以,你已经有了一台树莓派迷你电脑,你在想:“现在怎么办?”也许这是一个礼物。也许你听说过这个“覆盆子馅饼”,并决定找出所有的骚动是怎么回事。也许你已经对计算机有经验了,但对 Linux 或 Python 却没有。也许你是一个 Linux 极客,从来没有用几行代码和正确安装的硬件和软件做过伺服运动或点亮过 LED。或者,也许你对电脑的熟悉程度只达到了查看邮件和上网冲浪的程度,但却渴望了解更多。或者(这是我最喜欢的场景之一)你是一名教育家,对教育下一代计算机、编程和一般技术感兴趣。

不管是什么情况,欢迎!你即将加入一个俱乐部——恐怕不是一个特别排外的俱乐部,因为加入的费用只有大约 35 美元和一点创造力,但仍然是一个俱乐部——由学生、教师、业余爱好者、艺术家和工程师组成。作为这个俱乐部的成员,你将能够与任何愿意听你唠叨的人智能地讨论包管理器ARM 处理器点配置文件。你将熟悉伺服系统、发光二极管和单芯片相机。也许最重要的是,您将能够连接到您的新迷你计算机,使用许多编程语言中的一种对其进行编程(尽管本书仅涉及 Python),构建项目,并将这些项目与 Pi 进行交互,使其能够与物理世界进行交互并做一些非常酷的事情。

凭借这本书,我特此介绍你加入这个俱乐部。您的经验并不重要,因为我将一步一步地引导您完成设置 Pi 的过程,这样您就可以轻松地使用它。我将给你一个坚实的 Linux 背景,让你对幕后发生的事情有一个概念,我将用整整一章来讲述 Python,这种看似强大的脚本语言被脸书、谷歌甚至 NASA 这样的科技公司所使用。我还打算向你介绍电子学的基本原理,这是许多技术工程书籍要么掩盖,要么完全忽略的东西。有安全因素要考虑(例如,由于短路脂肪电池,我几乎有几次小爆炸)以及良好的建筑实践;您将了解好的和坏的焊点之间的区别,如何避免用 X-ACTO 刀切掉手指,以及 40ω和 40KΩ电阻之间的区别。

当然,如果你已经熟悉了所有这些介绍性的项目,可以随意跳到好的部分——项目。大部分都可以在一个周末左右完成,我也尽量把成本控制在合理的范围内。所有程序都是用 Python 编写的。在每一章的开始,我会给你一个购物清单和在哪里购买零件的建议,然后我们就开始建造。这些项目不一定要建立在彼此的基础上,也不一定要按顺序进行,尽管从第一个项目到最后一个项目,它们的复杂性会增加。

你能用 Pi 做什么样的项目?一个更好的问题可能是什么样的项目不能用 Pi 来做。它已经被用于从网络服务器到汽车电脑(汽车电脑)到集群计算到嵌入式机器视觉设备到 CNC 控制器的所有东西。。。这个清单还在继续。我希望读完这本书后,你会有自己的一些想法,以及将这些想法付诸实践所需的技能。

不管你拿起这本书的原因是什么,你的主要目标应该是获得乐趣和学习一些东西。希望一路走来能帮上忙!

树莓派的历史

对一些读者来说,树莓派似乎是新的;有相当多的人不知道这是什么。即使是现在,在第一个圆周率产生七年后,大量在线文章的开头都是这样的:“树莓圆周率是一个小型的信用卡大小的计算机,爱好者已经开始使用它了。。."这与 Arduino 形成了鲜明的对比;大多数关注时事的人至少听说过它,即使他们不知道它是什么或它的用途,因为它自 2005 年以来一直存在,并在世界各地的爱好者、极客和自己动手者中获得了巨大的声音追随者。

Arduino

对于那些不知道的人来说,Arduino 是一个微控制器平台,有许多不同的外形和尺寸,安装在 PCB 上,可以轻松插入大多数计算机的 USB 端口。它允许用户通过一种叫做草图的程序中类似 C 的编程语言对板载 Atmega 芯片进行编程来做各种事情。典型的 Arduino 草图可能如下所示:

#include <Servo.h>

void setup()
{
     myservo.attach(9);
}

void loop()
{
    myservo.write(95);
    delay(1000);
    myservo.write(150);
    delay(1000);
}

这个程序将前后移动一个连接的伺服电机(一个可以通过软件精确控制的小电机),两次移动之间有一秒钟的延迟。

在计算能力方面,Arduino 不如 Pi 强大,但它也是一种完全不同的动物,因为它是一个微控制器,而不是一台计算机,所以比较它们有点像比较斑马和鳄梨。然而,这两台机器确实很好地互补,我将在第 14 章讨论如何互补。

正如我所说的,树莓派已经存在了几年——确切地说是七年。有几种不同的型号可供选择,新的改进版本大约每隔一年发布一次。

圆周率的创造者——埃本·厄普顿、罗布·穆林斯、杰克·朗和艾伦·迈克罗夫特——在 2006 年首次提出了教学用廉价电脑的想法。他们都来自英国剑桥大学,他们担心 Commodore 64、Amiga 和 Spectrum 等廉价个人电脑的消亡会对年轻人的编程能力产生负面影响。由于台式机和笔记本电脑的价格高达数百或数千美元,儿童和青少年被禁止在家庭的主电脑上练习编程,因为他们害怕破坏它。

与此同时,厄普顿和其他人意识到许多大学计算机科学课程已经简化为“微软 Word 101”和“如何使用 HTML 创建网页”这四位创始人希望提高新生的编程知识水平,因此,计算机科学和工程课程可能会变得更强大,更适用于现实世界中的 STEM 领域。

显然,更便宜的计算机是必要的。他们尝试了微控制器和各种芯片、试验板和印刷电路板(见图 1-1 ),但直到 2008 年这个想法才变得更加可行。由于移动设备的爆炸,芯片变得更小、更便宜、更强大。这些芯片使他们能够设计一种能够支持多媒体的设备,而不仅仅是命令行编程,他们认为这对于吸引所有年龄的学生很重要。年轻人更有可能对支持媒体的设备感兴趣,因此也更有可能尝试在这种设备上编程。

img/323064_2_En_1_Fig1_HTML.jpg

图 1-1

Eben Upton 的 2006 年树莓派原型(图片树莓派基金会)

2008 年,最初的四位创作者与皮特·洛马斯(Pete Lomas)和大卫·布拉本(David Braben)一起成立了树莓派基金会(the Foundation),三年后第一个大规模生产的 Pi 下线。

注意

名称 Raspberry Pi 是对早期以水果命名的微型计算机数量的认可,如苹果和橘子,而 Pi 来自 Python 脚本语言,这一直是 Pi 设计的一个组成部分。

一年之内,基金会售出了超过一百万台。创始成员已经多次谈到他们是如何被对他们设备的爆炸性兴趣惊呆的。他们最初的目标是把一个便宜的可编程设备放在教育工作者和他们的学生手中,这个目标已经实现了,但是 Pi 的意义还不止于此。显然,他们不是唯一失去在更便宜的机器上编程能力的人;世界各地的爱好者(包括你真正的爱好者)用订单淹没了 element14、Premier Farnell 和 RS Electronics 以至于预购 Pi 的人不得不等待长达 6 个月的时间,以供不应求。(在撰写本文时,Pi 的最新型号之一 Pi Zero W 仍然只能在每个客户一台的基础上提供。)许多客户可能是现在或以前的程序员,渴望玩一台新的、小的、功能强大的计算机。例如,我第一次学会用 BASIC 语言编程是在 Commodore VIC-20 上,这是一台具有令人印象深刻的 5KB 内存的计算机。

除了教育,圆周率还有无数的其他用途,就像树莓圆周率基金会的关于我们页面上写的那样:

  • 我们得到了教育界的极大关注、支持和帮助,对于来自远离我们最初目标的机构和个人的大量咨询,我们感到很高兴,也有点受宠若惊。发展中国家对 Raspberry Pi 感兴趣,将其作为生产力设备,用于那些根本负担不起运行传统台式 PC 所需的电源和硬件的地区;医院和博物馆已经联系我们,了解如何使用 Raspberry Pi 来驱动显示设备。严重残疾儿童的父母和我们谈论过监控和辅助应用;似乎有 100 万人拿着热烙铁想要制造一个机器人。

幸运的是,在很大程度上,供应已经安全地赶上了需求。购买圆周率不再需要等待,除了零 W 之外,每位顾客也不再有购买一个圆周率的限制。有数不清的“帽子”(形状合适的配件市场附加板,具有各种功能),以及一个摄像头板和一个官方触摸屏显示器,它们都可以插入 Pi 上的端口。创始人还积极鼓励其他公司复制他们的模式,这可能是今天有这么多小型单板计算机的主要原因。

探索圆周率

由于有许多不同的设计可供使用,所以不可能只写一个章节来详尽地说明 Pi 的内置部件和设计。不过,我将保持这一节的篇幅,只讨论最近的三个版本:Pi version 3、Zero 和 Zero W,因为 Zero 和 Zero W 的设置几乎完全相同,所以我们只需要描述其中一个。所有这些主板的价格都很低;版本 3 目前约 35 美元,零约 5 美元,零 W 为 10 美元。2018 年 3 月 14 日,也被称为圆周率日,树莓派基金会发布了圆周率第三版的更新,即 3 B+这个新版本对最初的版本 3 进行了一些升级,包括双频 WiFi、稍快的 CPU (1.4GHz)和以太网供电(PoE)功能。由于这个版本仍然是非常新的,因为它的外形几乎与最初的版本 3 相同,并且它的升级不会影响本书中的任何项目,所以我不会在这一点上提及它。

圆周率的大小这些年来没变过。Pi 3 的尺寸与 Pi 1 相同:85.6 毫米 x 56mm 毫米 x 21mm 毫米。Pi Zero 和 Zero W 稍微小一点:30mm x 65mm x 3.5mm(没有 USB 和以太网端口使得厚度差异巨大)。最新的 Pi 重了一点——45 克,而原来的是 31 克——但幸运的是,当你试图将新的 Pi 安装到旧的案例或项目设计中时,重量可能不会成为因素。

看一下图 1-2 ,我将带你从 GPIO 引脚开始,顺时针方向浏览一下电路板。

img/323064_2_En_1_Fig2_HTML.jpg

图 1-2

树莓 Pi 3

GPIO 引脚

正如你在图中所看到的,电路板的小空间里装了很多东西。你可以看到,沿着顶部,从 Pi 的早期版本到当前模型的最大改进之一:从 26 个增加到 40 个 GPIO(通用输入/输出)引脚。这些引脚允许您将 Pi 连接到任何数量的物理扩展,从 led 和伺服电机到电机控制器和扩展板(通常称为“hats”)。对于普通的台式机或笔记本电脑,与这样的物理设备接口几乎是不可能的,因为串行端口在较新的设备上几乎已经消失,并且不是每个人都能够为 USB 端口编写低级设备驱动程序。然而,Pi 带有预安装的库,允许您使用 Python、C 或 C++访问 pin,如果您不喜欢预安装的版本,还可以使用其他库(例如 PiGPIO 和 ServoBlaster)。

USB 和以太网

接下来,我们将沿着外部边缘看到两对 USB 端口和以太网端口。它们都连接到 LAN 9514(USB 端口左侧的芯片),提供 USB2.0 和 10/100 以太网连接。与所有其他 pi 一样,该芯片充当 USB 到以太网的适配器,这使得板载以太网能够工作。

音频插孔

板上的 3.5 毫米音频插孔可用于任何标准耳机。如果可用,HDMI 声音通过 HDMI 接口传送。声音输出也可以通过 I 2 S 实现。I 2 S 不在本书讨论范围内,但它是一种用于将数字音频设备连接在一起的串行接口标准。

相机连接器

板上的摄像头连接器允许您将官方的 Raspberry Pi 摄像头板(图 1-3 )或 NoIR(红外)摄像头板连接到 Pi。

img/323064_2_En_1_Fig3_HTML.jpg

图 1-3

Raspberry Pi 相机板

高清晰度多媒体接口

摄像头电路板连接器后面是 Pi 的 HDMI(高清多媒体接口)端口。许多 Pi 爱好者认为,这是 Pi 从一开始就与众不同的地方,因为它总是能够显示高清图形。最新版本的 Pi 板载 400MHz Broadcom VideoCore IV GPU,使其能够以高达 60fps 的速度输出全高清视频。它可以支持蓝光质量的播放,并在芯片上支持 OpenGL 和 OpenVG 库,虽然它没有 H.265 解码硬件,但 GPU 运行速度足够快,可能能够在软件中解码 H.265。

力量

继续顺时针方向,我们来到微型 USB 电源输入端口。类似于以前版本的 Pi,你可以用一个标准的手机充电器给你的 Pi 供电,但要确保它至少能提供 2A。Pi 3 本身可能不会使用那么多电流,但如果四个设备插入四个 USB 端口,它肯定可以。

你也可以用电池给 Pi 供电(我倾向于用 LiPos),但是有一个警告:Pi 没有板载电源调节器!如果你习惯使用 Arduino,你知道你可以插上 9V 电池,然后上路。如果你用圆周率来尝试,迎接你的将是一股神奇的烟雾,你需要买一个新的圆周率。我将讨论移动 Pi 必不可少的项目中的电压调节器。

显示

电路板顶部的最后一个连接器是 DSI 显示器连接器,用于连接官方的 Raspberry Pi 7 "触摸屏显示器。这款显示器于 2015 年发布,最终满足了 Pi 爱好者的需求,他们需要一种简单的方式来与 Pi 交互,而不必带着巨大的显示器和键盘。如果你没有显示器或不需要触摸屏界面,你仍然可以自由使用普通的 HDMI 显示器和 USB 键盘和鼠标。

片上系统

整个 Pi 上最重要的一块是中间的大黑芯片,也称为 SoC ,或 芯片上的系统。Pi 的芯片是 Broadcom PCM2837,配有 1.2GHz ARM Cortex A53 四核集群。这甚至比最近的 Pi 都有了巨大的改进,其中最大的改进是在多线程处理方面。不幸的是,一个代价是新芯片消耗了更多的能量。如果你正在寻找低功耗,你可能会更好地与老型号或与零或零 w。

sd 卡

最后,在电路板的底部是 microSD 卡插槽。Pi 最大的节省空间的特点之一是它没有真正的硬盘。SD 卡的作用类似于固态硬盘(SSD)。这种形式因素在 Pi 版本的过程中有所变化;当前版本只支持 microSD 卡,没有弹簧加载。您需要使用至少 4GB 的卡来最小化安装 Raspbian(Pi 的首选操作系统)以便在 Pi 上工作,建议使用 8GB 的卡。我已经能够在 Pi 上使用高达 64GB 的卡,但是您的结果可能会因卡的制造商而异。如果你担心数据降级或启动失败,坚持使用名牌卡。

不可见

在 Pi 3 的主板上看不到的一点是它内置的 WiFi 和 ble(蓝牙低能耗)功能。这些由 Broadcom BCM43438 芯片提供,该芯片提供 2.4GHz 802.11n 无线局域网、BLE 和蓝牙 Classic 4.1 无线电支持。对我来说,这是对原始 Pi 的巨大改进,因为我不再需要购买和配置 USB WiFi 转换器,同时失去了 USB 端口,蓝牙兼容性在构建物联网(IoT)应用时是一个巨大的便利。

Pi 零/零 W

这是对 Pi 3 的设置的一个相当详尽的介绍,但如果不看看 Pi 的最新兄弟 Pi Zero 和 Pi Zero W(图 1-4 ),任何新书都不会完整。圆周率零点于 2015 年 11 月推出,随后是零点 W。Zero W 基本上是与 Zero 相同的型号,只是内置了无线互联网。

img/323064_2_En_1_Fig4_HTML.jpg

图 1-4

圆周率零点 W

让我们快速浏览一下 Zero W 提供的一切。

通用输入输出接口

您可能会注意到的第一件事是缺少标题。为了降低成本,因为你只需为 0 支付 5 美元,为 0 W 支付 10 美元,Pi 基金会决定你必须自己焊接接头。这是一个很小的代价,因为它仍然拥有 40 个引脚,引脚排列与全尺寸 Pi 相同。

相机连接器

继续顺时针旋转电路板,您会发现 Pi 相机电路板的连接器。这里的区别是形状因素:Zero 的连接器比 Pi 3 上的连接器薄得多。Zero 仍然使用相同的相机板,但电缆连接不同。如果你打算把你的 Pi 相机和你的 Zero 一起使用,确保你订购了一根适配器线,大多数卖 Pi 配件的地方都有卖。

力量

在电路板的底部,您会看到两个微型 USB 端口。第一个是相机接口旁边的电源接口,就像较大的 Pis 一样。一个标准的手机充电器应该在这里做得很好,因为零不需要太多的电流。再次,像 Pi 一样,它没有而不是有一个电压调节器,所以请确保您只给它一个干净的 5V 电源。

通用串行总线

电源微型 USB 连接器旁边是微型 USB 端口。要在 Zero 上使用大多数外设,您需要购买微型转标准 USB 集线器。确保你得到一个不需要外部电源的,除非你打算使用像网络摄像头这样的耗电设备。在这种情况下,你需要一个有电源的集线器,因为零线不能提供太多的电流。

高清晰度多媒体接口

继续顺时针方向,在微型 USB 端口之后是迷你 HDMI 端口,这(显然)将需要一个迷你 HDMI 适配器。Zero 不像更大的 Pi 那样有单独的 GPU,但它仍然能够通过该端口输出完整的 1080p 输出。

sd 卡

最后,microSD 卡插槽是这个小小的板上的最后一个东西。像更大的 Pi 一样,你至少需要 4GB 的卡才能在 Zero 上做任何有价值的事情,我真的建议 8GB 或更大。

片上系统

电路板中央的黑色大芯片是 Broadcom PCM2835,配有 1GHz 的 ARM11 处理器。如果这些规格听起来很熟悉,它们应该是:它与最初的 Raspberry Pi 上封装的芯片相同,只是运行速度更快一些。价格点已经下降了一点,使其能够放置在像 Zero 这样的低功耗板上。

不可见

像 Pi 3 一样,Zero W 的无名英雄之一是内置的 2.4GHz 804.11n 局域网,BLE 和经典的蓝牙 4.1 功能。无线电芯片和 Pi 3 上的一样,但是天线有点不同,我想它看起来很快。如果你仔细观察 USB 输出和迷你 HDMI 端口之间的电路板边缘,你会看到一个小三角形。那个三角形实际上是在 PCB 的各层中切割出来的,是一个谐振腔,大小正好可以与 WiFi 无线电波进行交互。这是一个巧妙的想法,并有助于保持零小和便宜。

Zero 和 Zero W 都是令人难以置信的廉价设备,如果你打算用 Pi 做任何工作,我强烈建议每样都买一个或多个。对于价格,你真的不能击败你可以用他们两个做什么。

将 Raspberry Pi 与类似设备进行比较

你可能会问,是什么使 Pi 优于其他小型微型计算机,如 Arduino,也许还有 Beagleboard 系列设备?答案是 Pi 不一定比 ?? 更好;这些板中的每一个都占据了一个特定的位置,很难对它们进行比较,尤其是像 Arduino 这样的微控制器。Arduinos 非常适合创建简单的项目,甚至可以控制一个非常简单的机器人,在很多情况下,使用 Pi 来实现这种目的是多余的。至于其他的微型计算机,一个主要的区别是价格。与 Pi 密切相关的是 Beagleboard,但该板的建议价格超过 75 美元——比 Pi 高得多。购买 Raspberry Pi 意味着你在支持一个慈善组织,该组织旨在将廉价电脑送到世界各地的小学生手中,所以这也是存在的。

Pi 入门

我想你会同意,现在是把圆周率从盒子里拿出来的最好时机,如果你还没有的话。在开始之前,请继续阅读。

Pi 的硬件要求

让我们快速看一下 Pi 的要求是什么,然后我们将启动它。为了本章的目的——实际上,这本书的大部分内容——我将假设你,读者,使用的是圆周率 3 而不是零。大多数事情将保持不变;如果有明显的差异,比如在电源要求方面,我一定会提到它们。

连接电源

我已经提到了权力。Pi 需要 5V——不多也不少。再次,因为它值得重复:Pi 没有板载电压调节器!你不能插上 9V 电池或壁式电源插座就指望它能工作。要么使用能输出 5V 电压的手机充电器,要么从网上电子商店或购买圆周率圆周率圆周率圆周率圆周率圆周率圆周率圆周率的地方获得优质电源。电源输出也应至少为1.5A,最好是 2A。如果它没有足够的电源,请准备好面对你的 Pi 的一些古怪行为,比如鼠标和键盘无法工作,甚至完全无法启动。

添加显示器

你需要的下一个外设,至少在一开始,是一个具有 HDMI 或 DVI 功能的显示器。如果你只有 DVI 输入,那也没关系,因为 HDMI-DVI 转换器无处不在。在您设置好它并且安装了所有必要的软件之后,您可以在一个无头配置中运行 Pi。这意味着你可以从同一个网络上的另一台计算机上使用 SSH(安全外壳)甚至 VNC(虚拟网络计算)客户端登录。但是首先你需要一个显示器,这样你就可以看到你在做什么。一步步来。

添加 USB 集线器

虽然 Model 3 有四个 USB 端口,但您可能希望在某个时候添加一个 USB 集线器。如果你使用零,你肯定需要一个,至少在开始的时候。当你添加一个集线器时,性能会变得有点挑剔,因为当涉及到与 Pi 一起工作时,一些 USB 集线器已经被证明比其他的更好地工作。也许最重要的必要特征是集线器是外部供电的;这将防止你的 Pi 必须提供足够的电力给你那天决定插入的任何耗电设备。如果你没有空闲的网络中心,不知道该尝试什么,树莓派论坛通常是一个开始寻找的好地方( http://www.raspberrypi.org/phpBB3 )。在这里,像你一样的用户已经尝试了无数不同的品牌,并报告了哪些可以,哪些不可以,哪些需要稍微调整。幸运的是,集线器相对便宜,所以如果您尝试的第一个不工作,您可以在其他地方使用它,并用您的 Pi 尝试不同的。

*我在 Zero 上使用的是 MakerSpot 迷你 USB 集线器(图 1-5 )。

img/323064_2_En_1_Fig5_HTML.jpg

图 1-5

MakerSpot 迷你 USB 集线器

然而,在这里你应该照我说的做,而不是照我做的做,因为这个特殊的集线器是而不是外部供电的。它确实做了我需要它做的一切,而没有导致我的 Zero 遭受停电(由于电量不足而导致的怪异行为),所以请随意用它复制我的成功。

现在您已经为您的 Pi 配备了所有必要的外部部件,您已经准备好开始设置它了。

Pi 操作系统

Raspberry Pi 的默认操作系统(OS)——它设计使用的操作系统——是 Linux。Pi 3 可以运行物联网版本的 Windows 10,但设置它可能有点棘手,以我的经验来看,Pi 在 Linux 上运行得更好。如果你不熟悉 Linux 操作系统,不要担心——我们将在第二章中一探究竟。不过现在,我们知道 Linux 有几种版本,或者说是发行版 : Ubuntu(最流行的版本之一)、Debian、Mint、Red Hat、Fedora 以及其他一些不太知名的版本。Pi 使用了 Debian 的一个版本,名为 Raspbian。

因为 Pi 没有硬盘,所以您必须下载磁盘映像并将其复制到 SD 卡。Pi 将使用该映像进行引导,它还将充当内存/RAM。几乎任何大小的卡都可以,只要它至少有 4GB,如果你打算在卡上加载大量的额外软件,最好是 8GB 以上(是的,你最终会这样做)。正如我前面提到的,高达 64GB 的卡已经被证明可以正常工作;除此之外,你的结果可能会有所不同。建议你用名牌卡,而且应该是4 级,表示卡的速度。

格式化卡片

你的第一个任务是格式化卡,这样你的 Pi 就可以读取它。使用内置 SD 读卡器或 USB 适配器将 SD 卡插入电脑。现在,执行以下操作:

  • 【Windows 用户:在 https://www.sdcard.org/downloads/formatter_4/eula_windows/ 从 SD 协会下载格式化工具程序。使用所有默认设置安装并启动它。在工具的选项菜单中,将“格式大小调整”选项设置为“开”,确保您选择了正确的 SD 卡,然后单击“格式”

  • 对于 Mac 用户:从 https://www.sdcard.org/downloads/formatter_4/eula_mac/ 下载 Mac 版格式化工具。双击下载的.pkg文件,使用所有默认设置安装该工具。一旦安装完毕,打开它,选择“覆盖格式”选项。确保您选择了正确的 SD 卡,然后单击“格式化”

安装操作系统

既然卡已正确格式化,您可以在上面安装操作系统了。大部分用户可以使用 http://www.raspberrypi.org/downloads 的 Pi 基金会的 NOOBS(全新开箱即用软件)。但是,如果您愿意,您可以直接下载 Raspbian 图像本身。甚至还有一个 Raspbian Lite 操作系统,它的内存占用要小得多,非常适合 Zero 和 Zero W 等较小的系统。NOOBS 系统在第一次启动时,实际上会为您提供一个安装操作系统的选择,包括两个版本的 XBMC (Xbox Media Center)、Pidora 和 Raspbian。出于本书和后续章节的目的,我们将安装 Raspbian。

一旦你下载了你选择的操作系统(NOOBS:1.3 GB;Raspbian: 1.7GB),使用您选择的解压缩实用程序将其解压缩(Windows:右键单击,“Extract all”;Mac:双击)。然后,将提取的文件复制到您的 SD 卡上。

就这样。您的 Pi 现在可以启动了。

连接外围设备

准备好连接所有这些美妙的组件了吗?别急,莫克·萨比。连接外设有一个优先顺序。这可能看起来很奇怪,但有可能(即使可能性极小)以错误的顺序连接电源、显示器和其他部件可能会导致电压尖峰并烧毁您的主板。所以,习惯于按照这个顺序把事情联系起来,这样你就不会有潜在的麻烦了。顺序如下:

  1. 插入 SD 卡。

  2. 连接显示器。

  3. 连接 USB 外围设备(键盘、鼠标、集线器)。

  4. 连接以太网电缆。

  5. 连接电源。

事实上,这里要记住的最关键的细节是最后接上电源。你或许可以在其他人身上蒙混过关,但权力应该永远留在最后。

没有开关;一旦你插上电源,板上的 led 应该开始亮起,你应该在你的监视器上短暂地看到一个彩虹屏幕(图 1-6 )。

img/323064_2_En_1_Fig6_HTML.jpg

图 1-6

Pi 彩虹启动屏幕

这个屏幕实际上是由 Pi 的固件在初始化板载 GPU 时生成的。GPU 在屏幕上绘制四个像素,然后对它们进行纹理化,从而产生多色正方形。您应该只看到很短的一段时间,然后随着 Pi 继续其引导过程,出现一个滚动的文本列表。

配置 Pi

当您第一次使用 NOOBS 启动 Pi 时,您会看到一个有六个选项的选择框:Archlinux、OpenELEC、Pidora、RISC OS、RaspBMC 和 Raspbian(尽管这些选项可能会根据您阅读本文的时间而发生变化)。用鼠标选择 Raspbian,然后单击窗口左上角的“安装操作系统”按钮。在随后的弹出框中单击“是”进行确认,然后等待图像写入 SD 卡。这可能值得一看,因为进度窗口有一些提示,你可以在等待时阅读。如果您刚刚下载了 Raspbian 映像,Pi 应该会自动引导到桌面(图 1-7 )。

img/323064_2_En_1_Fig7_HTML.jpg

图 1-7

Pi 的默认桌面

一旦您的 Pi 已经启动,并且您在桌面上,您应该首先访问的是软件配置工具,也称为raspi-config。这可以通过键入

$ sudo raspi-config

在一个终端。(要启动终端,请单击菜单栏中的终端图标。)这个工具允许您对配置进行更改,比如扩展文件系统、启用 SSH 和摄像头,以及设置 Pi 的语言环境。最后一项(可从 Localisation Options 子菜单中访问)比您想象的更重要,因为 Pi 的默认区域设置(以及相关的键盘布局)是在英国,这意味着如果您在美国,当您第一次按 Shift > 2 时,您会感到不愉快的惊讶,因为您以为会得到“@”符号,但却看到双引号(")。

要浏览raspi-config工具(图 1-8 ,用箭头键选择您想要更改的选项,按 Tab 键,然后按 Enter 键选择您的选项。一定要启用摄像头,因为您稍后会用到它,以及 SSH 访问和 VNC 功能——所有这些都可以在接口选项子菜单中找到。我还会启用 I2C、SPI 和串行,因为它们总是很方便,以后还会用到。随心所欲地玩所有的设置,记住你不能真的破坏任何东西。如果你把你的卡做成砖块,使它无法使用,只需使用 SDCard 工具重新格式化它,并把操作系统复制到它上面,这样你就可以重新开始。稍后,你可能要更加小心,但我会告诉你如何备份你的卡,这样你就不会丢失任何设置,如果你做一些愚蠢的事情。

img/323064_2_En_1_Fig8_HTML.jpg

图 1-8

raspi-config

当你玩完raspi-config后,选择“完成”并按回车键。

您的 Pi 现在已经启动并运行。恭喜你,给自己一个鼓励!尽情享受,但不要太舒服。你的下一个任务应该是确保一切都是最新的。大多数 Linux 发行版都会定期发布更新和升级,Raspbian 也不例外。从 Pi 基金会提供 NOOBS 或拉斯扁的图像供下载到今天,很有可能已经对软件和内核进行了几次重要的升级。

要更新 Pi,请确保您的以太网电缆已插入并启动终端(单击终端图标或按 Ctrl–Alt–t)。在提示符下,键入

$ sudo apt-get update

当 Pi 刷新它的软件列表时,你会看到一行行的文本流畅地流过。完成后,将返回“$”提示。此时,键入

$ sudo apt-get upgrade

同样,文本行应该滚动过去。如果可以下载新软件,Pi 会询问您是否要下载并安装它。按 Enter 键(默认选项)。当它完成并返回到$提示符时,一切都应该是最新的版本。根据更新的内容,可能会提示您重新启动。这样做,你就会跟上时代。

注意

当我提示您在终端中输入文本时,当您看到“$”字符时,您实际上不应该键入美元符号。这是由于您使用的 shell 环境而在终端中出现的提示符。

关闭码头

在我们开始讨论 Linux 之前,我们先来讨论一下关机。事实上,关闭 Pi 真的没有必要;这是一个如此低功耗的设备,设计师只是希望你让它运行。不过,你可以把它关掉,为了节省一点钱,也许还有你的 Pi,我建议你在用完之后把它关掉。由于没有“关闭”开关,Pi 实际上被设计为只需拔掉插头就可以关机,应该不会发生什么不好的事情(假设您已经保存了工作,没有在忙什么,等等)。但是仅仅拔掉它就让我们许多电脑类型的人畏缩不前,所以让我教你真正的,正确的关机方法。打开终端,在提示符下键入

$ sudo shutdown now

这将使处理器经历正确的关闭序列,终止正在运行的进程,停止线程,等等。完了,拔下圆周率,真的很安全。

如果希望从终端重新启动,请键入

$ sudo shutdown –r now

将重新启动 Pi。

摘要

现在,您已经了解了 Pi,安装了它的操作系统,并对它进行了更新,使其寿命缩短了一英寸。还向您介绍了raspi-config工具,您甚至还使用了一点命令行界面(CLI)。是时候深入了解一下 Linux 了。*

二、Linux 就在你的身边

Raspberry Pi 使用 Linux 作为它的标准操作系统,这意味着如果你对这个令人敬畏的操作系统一无所知,你将不得不去学习。别担心——我会尽可能让这件事不那么痛苦。

无论你对 Linux 有什么先入之见,你都可以忽略它们。从一开始,Linux 就被认为是“极客的操作系统”,让人联想到穿着短袖衬衫、笔挺的人在键盘上敲敲打打,屏幕上满是文本,在地下室深处的某个地方,一排磁带驱动的电脑硬盘柜开始运转。(见图 2-1 。)在背景中,一个 20 面骰子滚过桌子,还有一个论点的轻声低语:“不,韩先开枪!”

img/323064_2_En_2_Fig1_HTML.jpg

图 2-1

Linux 用户的游乐场(2006 Marcin Wichary)

然而,不要害怕。虽然我们中的一些人仍然热情地拥抱这种文化及其所代表的一切,但这并不意味着你必须这样做。自从 Linux 首次推出以来,它已经走过了漫长的道路,现在它不仅是一个真正的操作系统的发电站,而且非常用户友好(至少,它的大多数发行版都是如此)。Linux 最受欢迎的版本是 Ubuntu 和 Mint。两者在视觉上都与 Windows 和 Mac 非常相似,以至于许多人觉得切换到它们既有趣又容易。另一个流行的 Linux 版本是 Debian,它是 Pi 的操作系统 Raspbian 所基于的发行版。最初,Debian 是唯一一个真正“开放”的 Linux 发行版——允许任何开发者和用户做出贡献。它仍然是非商业实体的最大的 Linux 发行商。

好了,喇叭响够了。为了真正使用 Pi,您至少需要对 Linux 及其工作原理有一个基本的了解。那么,我们开始吧。

Linux 的故事

Linux 是一个松散地基于 Unix 操作系统的操作系统。它一直是免费和开源的,它的创建者 Linus Torvalds 在 1991 年首次发布了它。它是用 C 编程语言编写的,最初设计用于运行在 Intel 基于 x86 的计算机上。在随后的 20 多年里,它被移植到了所有可以想象的设备上,从大型机和超级计算机到平板电脑、电视、冰箱和视频游戏机。Android 操作系统是建立在 Linux 内核之上的,Linux 内核是构建操作系统的代码块。

像大多数计算机软件一样,Linux 并非诞生于黑洞之中。它起源于 Unix、BSD、GNU 和 MINIX 等操作系统和内核。事实上,托瓦尔兹曾经说过,如果 GNU 内核已经完成,或者如果 BSD 在 20 世纪 90 年代早期已经可用,他可能就不会编写自己的内核了。他从 MINIX 开始他的内核工作,并最终添加了许多 GNU 软件应用。他还将自己的许可权转到了 GNU GPL,这表明只要代码是在类似的许可下发布的,就可以被重用。

在接下来的几年里,Linux 在用户接受度和设备方面都得到了普及。上述所有设备都运行 Linux,这是世界上最广泛采用的操作系统。

Pi 上的 Linux 入门

为了与您的 Pi 交互,您将使用终端做大量的工作——也称为命令行界面,或 CLI。启动并运行您的 Raspberry Pi 桌面后,双击终端图标启动它。因为您已经登录,所以不会要求您输入用户名和密码;相反,提示符会显示如下内容:

pi@raspberrypi:~ $

这是命令行界面(图 2-2 )。它告诉您,您是用户“pi”,登录到主目录中的机器“raspberrypi”(终端对“home”的简写)。

img/323064_2_En_2_Fig2_HTML.jpg

图 2-2

树莓码头

如果您在不同的目录中,提示符将显示该目录,例如

pi@raspberrypi:~/Pictures $

Linux 文件和文件系统

作为一个操作系统,Linux 完全是围绕文件和文件系统构建的。文件是由文件名和位置标识的任何信息片段,可以是文本、图像、视频或其他。该位置也称为目录路径,有助于保持每个文件与所有其他文件完全不同,因为该位置在技术上是文件名的一部分。例如,

/wdonat/Desktop/MyFiles/file.txt

不同于

/wdonat/Desktop/MyOtherFiles/file.txt

尽管两者都叫file.txt。文件名也区分大小写;/file.txt不同于/FILE.txt,也不同于/File.txt。您将会熟悉五类文件:

  • 包含您创建的信息的用户数据文件,如文本文件或图像

  • 包含系统使用的信息的系统数据文件,如登录、密码等

  • 目录文件,也叫文件夹,可以包含文件和其他目录。包含在目录中的目录被称为子目录,它们可以嵌套到你所能想到的程度。

  • 代表硬件设备或操作系统使用的一些占位符的特殊文件

  • 可执行文件,即包含操作系统指令的程序或外壳脚本

Linux 中的整个文件系统都包含在一个根文件夹中,用一个/表示。在该文件夹中有子文件夹,例如bin/home/proc/var/dev/。每个目录下都有更多的子目录。事实上,如果您可以缩小并以三维方式查看文件系统,它看起来就像一棵巨大的倒置的树。/home/文件夹是您的默认主目录,每个用户在 Linux(和 Unix)系统上都有一个。在该目录中,您可以自由地创建、执行和删除文件。如果您需要操作、编辑或删除系统文件,您可能需要作为根用户登录或者执行命令sudo

Root 用户对 sudo

在每个 Linux 安装中,都有一个被指定为 root 的用户,他能够管理系统上的所有文件,包括系统级文件。例如,大多数用户帐户不能编辑/var/目录中的文件,但是根用户可以。由于这种能力和误用它的可能性(甚至是意外),Linux 用户不会以 root 身份登录,除非绝对必要;当他们这样做时,他们登录,做他们需要做的事情,然后再次注销。Linux 极客中有句话:“只有 noobs 才以 root 身份登录”;换句话说,只有新手才能作为根用户登录并保持登录状态。

不过,作为根用户登录有一个快捷方式:sudo. sudo代表superuserdo,它只是告诉系统像根用户一样执行命令。系统将询问 root 密码,然后执行该命令。同样,系统不会仔细检查你是否真的想这么做,所以当你使用sudo时,要加倍小心,在你按下 Enter 键之前,你要知道你刚刚输入的命令的结果!

命令

为了使用 Linux CLI,您可以使用诸如cdls这样的命令在文件系统中导航。运行程序的命令也是从终端运行的。表 2-1 中包含了您将经常使用并且应该学习的常用命令。

表 2-1

常见的 Linux 命令

|

命令

|

意义

|
| --- | --- |
| 限位开关(Limit Switch) | 列出当前目录中的文件 |
| 激光唱片 | 更改目录 |
| 显示当前工作目录 | 打印工作目录 |
| rm 文件名 | 删除文件名 |
| mkdir 目录名称 | 将目录命名为目录名 |
| rmdir 目录名 | 删除空目录 |
| 类别文本文件 | 在终端显示文本文件的内容 |
| mv oldfile newfile | 移动(重命名)旧文件新文件 |
| cp oldfile newfile | 将旧文件复制到新文件 |
| 人工命令 | 显示命令的手册 |
| 日期 | 读取系统日期/时间 |
| 回声 | 回显终端中键入的内容 |
| 可做文件内的字符串查找 | 使用正则表达式的搜索程序 |
| 日本首藤 | 作为根用户执行 |
| 。/ 程序 | 运行程序 |
| 出口 | 退出终端会话 |

表 2-1 中列出的大多数命令都是不言自明的,但有些命令需要更多的解释:

  • 毫无疑问,这是最重要的命令。如果您不确定一个特定的命令做什么或者它使用什么参数/标志,在您的终端中键入man command会显示 Unix 手册页,其中包含您想知道的所有信息。当您打开一个页面时,它通常以命令的名称开始,然后是命令的各种排列的概要、命令的详细描述、所有选项和标志,以及这些选项和标志的作用。当你处于手动视图时,只需按回车滚动,然后按 q 返回终端。

  • 这个命令列出了你所在的任何目录下的文件;使用像–l–a这样的标志包括文件许可和修改日期等信息。当您使用–l标志时,每个条目的第一部分显示如下

    drwxr-xr-x
    
    

    在这种情况下,这意味着条目是一个目录(d);拥有者可以读、写、执行文件(rwx);群组成员可以读取和执行文件(r-x);所有用户都可以读取和执行文件(r-x)。在我们使用 Pi 的大部分工作中,您将是文件的所有者,因此文件权限应该不会对您有太大影响。然而,有时你需要使一个文件可执行;这就是chmod命令的用途,但是我们将在另一章中进行讨论——比如处理家庭媒体服务器的那一章(第七章)。ls还有一些其他非常有用的标志。ls –F列出目录中的当前文件,但在所有本身是目录的内容后有一个尾随的“/”。ls –a列出所有文件,包括“隐藏”文件(名称以句点[ . ]或双句点[ .. ]开头的文件,通常不会在标准的ls显示中显示)。

  • 这个命令带你到你命名的目录,就像你想的那样。几个特殊的目录名包括cd ~,它带您到您的主目录(“”,或波浪符号,表示您的主目录),和cd ../,它带您到文件夹结构中的一个目录。换句话说,如果你在~/Desktop/MyFiles/目录中,输入

    cd ../
    
    

    会把你放在~/Desktop/目录中

    cd ../../
    
    

    会将您置于您的主目录(~/)中,并键入

    cd ../MyOtherFiles/
    
    

    会将您从桌面上的MyFiles目录中取出,放入桌面上的MyOtherFiles目录中。

小费

如果你简单地键入cd并按回车键,你将被带回你的主目录,无论你在哪里。

  • 这是一个需要知道的好命令。当你迷路时,pwd简单地告诉你你在哪个目录,给出的答案是从根目录开始的路径。当您在一个可能有重复文件夹名称的目录结构中有四五个文件夹时,这尤其有用,比如

    /Users/wdonat/Desktop/MyApplication/bin/samples/Linux/bin/
    
    

    终端提示符简单地显示

    pi@raspberrypi: /bin $
    
    
  • rm:使用命令rm就像把一个文件拖进垃圾箱,有一个重要的区别:对于所有的意图和目的,你不能撤销它,所以确定你真的想删除那个文件!

  • mkdirrmdir:命令mkdirrmdir创建和删除目录。rmdir的警告是目录必须是空的,否则操作系统不允许你删除它。但是,您可以将–p选项与rmdir一起使用,这将删除一个文件夹的(也是空的)父文件夹。例如,打字

    rmdir –p /foo/bar/this_directory
    
    

    将依次删除this_directory/bar/foo/

  • mvcp:命令mvcp虽然相当简单,但需要一些时间来适应。mv不移动文件,而是重命名文件,同时销毁旧文件。打字

    mv myfile.txt myfile2.txt
    
    

    myfile.txt重命名为myfile2.txt。在mv命令结构中,你可以指定目录级别,所以在某种意义上你可以mv将一个文件从一个文件夹转移到另一个文件夹。例如,假设你桌面上的MyFiles文件夹中有一个名为myfile.txt的文件。您可以通过键入以下内容来移动和重命名它(从文件夹内)

    mv myfile.txt ../MyOtherFiles/myfile2.txt
    
    

    myfile.txt将会从你当前的目录中消失,而它的副本,名为myfile2.txt,将会出现在你桌面上的MyOtherFiles文件夹中。

    cp类似于 mv,但它是复制而不是重命名,所以你不会丢失原始文件。同样,您可以指定目录级别,因此cp便于跨文件夹复制。例如,打字

    cp myfile.txt ../myfile.txt
    
    

    在你的桌面上放一份myfile.txt的拷贝(假设你还在Desktop/MyFiles/目录中)。)

  • cat:使用cat可以快速预览一个文件,比如一个文本文件,而不需要在文本编辑器中打开它。输入cat filename将会在你的终端上显示一个文件的内容,即使它不是文本文件。(试着对一个图像文件执行cat,会看到一堆乱码。)如果您想逐行预览文件,而不是一次将整个文件输出到您的终端,请使用more命令,或者使用less命令。这将用第一批文本填充屏幕,按下 Enter 键将在文件中前进,一次一行。

  • date:使用 date(不带参数)简单地将系统的日期和时间打印到终端。通过参数,它允许您设置日期和时间。

  • 这个命令仅仅是回应你在终端中输入的内容。在终端交互会话中,这不是一个非常有用的命令,但是当您编写 shell 脚本(在终端中运行的预先编写的命令集)时,它类似于计算机编程语言的print语句。

  • 虽然man可能是这些命令中最重要的,grep可能是最强大的。这是一个搜索程序,可以搜索文件和目录,使用你以正则表达式的形式给它的任何输入,并“管道”输出到屏幕或另一个文件。正则表达式的使用使得它如此强大;如果您不熟悉它们,正则表达式是构成搜索模式的一系列字符,通常这一系列字符看起来像外语。举个简单的例子,

    grep ^a.ple fruitlist.txt
    
    

    将在fruitlist.txt中搜索以“a”开头、后跟一个字符、后跟“ple”的所有行,并将结果打印到屏幕上。使用|管道,允许你将那些结果发送到不同的输出,比如一个文本文件。grep的力量和复杂性让你可以写下关于它的章节;现在,只要意识到它的存在。

  • ./ program:运行一个可执行文件的命令非常简单——只需键入一个句点,后跟正斜杠,再后跟程序名。请注意,这只适用于通过您的用户名可执行的文件;如果文件没有正确的权限或者根本不是可执行文件,它会给你一个错误。

  • exit:最后的重要命令简单来说就是exit。这会停止终端中正在运行的任何作业(也称为 shell ,并关闭终端本身。

练习:在 Linux 文件系统中导航

在下面的介绍性练习中,让我们练习用命令行在 Linux 的文件系统中移动。双击 Pi 桌面菜单栏上的终端图标,打开终端提示符(命令行提示符)(如图 2-3 所示)。

img/323064_2_En_2_Fig3_HTML.jpg

图 2-3

终端图标

当它打开时,通过键入以下命令确保您位于主目录中

cd ~

然后键入

pwd

终端应该打印出

/home/pi

现在,通过键入以下命令创建一个目录

mkdir mydirectory

然后,在不输入它的情况下,通过键入

mkdir mydirectory/mysubdirectory

如果您现在输入ls,您应该看到mydirectory被列为可用目录。你现在可以打字了

cd mydirectory/mysubdirectory

您将位于新创建的子目录中。

让我们测试一下echo函数。在终端中,键入

echo "Hello, world!"

终端应该响应

Hello, world!

顾名思义,echo只是重复你给它的论点。但是,您也可以将某些内容“回显”到其他输出格式;默认只是碰巧是屏幕。例如,您可以使用echo和‘>’操作符创建一个文本文件。类型

echo "This is my first text file" > file.txt

如果您随后通过键入ls列出您的目录的内容,您将会看到file.txt被列出。类型

cat file.txt

看到它的内容,你应该看到

This is my first text file

在终点站。输入以下命令,创建另一个名为file2.txt的文本文件

echo "This is another file" > file2.txt

现在,通过键入以下命令将第一个文件重命名为file1.txt

mv file.txt file1.txt

如果您现在列出当前目录的内容,您会看到file1.txtfile2.txt。你可以cat每个文件,以确保它们是你创建的。

接下来,让我们将file1.txt复制到文件夹结构中的上一级目录。类型

cp file1.txt ../file1.txt

如果您现在通过键入以下命令列出您的主目录的内容

ls ../../

你会看到file2.txt还在,虽然它已经从你当前的目录中消失了。恭喜你!现在,您已经成功地在 Linux 命令行(或 shell)中完成了最常见的文件操作!

说到 shells,Linux 在大多数发行版中都有几个可用的。

Linux 中的 Shells

Linux 中的 shell 有类似于 Bourne shellC shellKorn shell 的名字。shell 只是用户和操作系统之间基于文本的界面,允许用户直接对文件系统执行命令。每种外壳都有其优点和缺点,但是如果说一种外壳比另一种外壳更好,那将是一种误导。它们只是提供了做同一件事的不同方式。 Bourne-again shell ,也称为 bash ,是 Bourne shell 的替代版本,是大多数 Linux 版本的默认版本,包括 Pi 的 Raspbian。它可以通过其登录提示“$”来识别。Bash 有一些键盘快捷键,如果您在终端中进行大量编辑和文件操作,它们会变得非常方便,就像我们在项目中将要做的那样。(参见表 2-2 。)

表 2-2

Bash 键盘快捷键

|

键或组合键

|

功能

|
| --- | --- |
| Ctrl + A | 将光标移动到行首 |
| 控制 + C | 停止当前正在执行的进程 |
| Ctrl + D | 注销;相当于退出 |
| Ctrl + E 组合键 | 将光标移动到行尾 |
| 控制 + H | 删除光标前面的字符 |
| Ctrl + L | 触发器的清零端 |
| 控制 + R | 搜索命令历史记录 |
| Ctrl + Z 组合键 | 暂停一个项目 |
| 向左/向右箭头 | 将光标向左/向右移动一个字符 |
| 向上/向下箭头 | 滚动浏览以前的命令 |
| Shift +向上翻页/向下翻页 | 在终端输出中向上/向下移动一页 |
| 标签 | 命令或文件名完成 |
| 标签标签 | 显示所有命令或文件名的可能性 |

同样,大多数快捷方式都是不言自明的,但最后两个需要一些额外的解释:

  • 当你正在键入一个长文件名时,按下 Tab 键将会为你完成文件名或者为你提供一个选择列表。例如,如果你在/Desktop/MyFiles/目录下,想快速浏览myextralongfilename.txt文件,只需输入cat myextr,然后按 Tab 键。Bash 将为您填写文件名,假设没有其他文件有类似的开头。如果还有其他以myextr开头的,bash 会发出报错声;在这种情况下,再次按 Tab 键可查看选项列表。

  • 这个快捷键也适用于命令。在您的终端中,键入l并按 Tab 键两次。Bash 将用所有以“ l 开头的可用命令来响应(这可能是一个相当长的列表。)您可以通过一次添加一个字母并再次按 Tab 键两次来重复该过程。shell 将填充所有可能的命令或文件,为您提供所有可能结果的预览。

包管理器

当您需要在 Windows 中从在线源安装程序时,通常会下载一个。exe 或。msi 文件,双击它,并按照说明安装程序。类似地,如果你使用的是 Mac,你可以下载一个. dmg 文件,然后将解压后的文件复制到你的硬盘上,或者使用附带的安装包。

然而,Linux 有一点不同。Linux 使用包管理系统或包管理器来跟踪它的软件。软件包管理器用于为操作系统下载、安装、升级、配置和删除程序。大多数软件包管理器维护一个已安装软件的内部数据库以及所有的依赖和冲突,以防止安装软件时出现问题。包管理器因发行版而异。Debian(和 Pi)使用 aptitude,而 Fedora 使用 RPM 包管理器,Puppy Linux 使用 PETget。如果你有玩下载游戏的经验,可能对 Steam 游戏比较熟悉;你可能会惊讶地发现 Steam 的接口是一个包管理器的变种。大多数包管理器都有命令行和图形界面。例如,Ubuntu 使用 Synaptic 前端作为其智能管理器。

像 Ubuntu 一样,Raspberry Pi 使用 aptitude 包管理器,你可能会在终端中使用它来完成大部分工作。安装一个软件的常用命令是

sudo apt-get install package name

这将指示经理执行以下操作:

  1. 确定哪个软件源或存储库中有请求的文件。

  2. 联系存储库并确定需要哪些依赖项。

  3. 下载并安装这些依赖项。

  4. 下载并安装所需的软件。

如果这看起来很容易,那就应该——应该是这样。当您请求一个不包含在您已安装的软件库中的软件时,您可能会遇到问题,但是即使这样通常也是一个简单的解决方法。如果发生这种情况,只需键入

sudo add-apt repository repository name

进入你的终端。完成后,输入

sudo apt-get update

让您的软件包管理员了解新的存储库并从中获取可用软件包的列表,然后输入

sudo apt-get install package name

又来了。幸运的是,Raspbian 中包含的默认存储库(或 repos )包含了你需要的大部分软件,所以(至少对于这本书来说)你可能不会遇到这个问题。

文本编辑器

不像 Windows 和 Mac 有记事本、写字板和文本编辑,Linux 有几种可能的文本编辑器。大多数发行版上都安装了一个名为 gedit 的标准编辑器。它不仅相当轻便,而且也不包括在 Pi 中。Pi 的内置文本编辑器 Leafpad 相当不错。您可能还会发现自己对 nano 越来越熟悉,nano 是 Pi 上预装的另一个文本编辑器,具有非常直观的界面。但是如果您在 Pi 上做任何严肃的编程工作,您可能最终想要升级到 Linux 的两个强大功能之一:vi 或 emacs。

vi 和 emacs 不仅是强大的编辑器;它们也可以用作 ide(集成开发环境),带有关键字文本着色/语法突出显示和单词补全。两者都是可扩展和可定制的;例如,emacs 有 2000 多个内置命令,而 vi 可以通过其许多端口和克隆进行定制。事实上,vi 的克隆版本之一 Vim (Vi 改进版)几乎包含在每个 Linux 发行版中,也是我在这里要讨论的版本,因为它比它的前身 Vi 更像一个 IDE。Emacs 可以由用户使用 Lisp 扩展进行编程,但是对于您可能有的每一种美感来说,它都非常类似于 vi。

然而,emacs 和 Vim 之间正在进行一场战争。Linux 和 Unix 用户强烈喜欢其中的一个,当讨论/争论各自的利弊时,他们会变得异常兴奋。作为一个认真的作者,我将在这里向您介绍这两个程序,但是作为一个铁杆 emacs 用户,我将尽我所能使您的选择远离 Vim 这个垃圾。当我们在整本书中讨论程序和脚本时,我不会提及它们是如何编写的,而仅仅是最终结果是什么样子。您甚至可以决定您喜欢 Pi 的默认文本编辑器,这也完全没问题。

Vim 对 emacs 对 nano

Vim 是一个模态编辑器。它有两种模式:插入正常。在插入模式下,您的击键成为文档的一部分。正常模式用于控制编辑会话。例如,如果您在正常模式下键入“I ”,它会将您切换到插入模式。如果你再次输入一个“I ”,一个“I”将会出现在光标的位置,就像你所期望的文本编辑器一样。通过在这两种模式之间来回切换,您可以创建和编辑您的文档。

另一方面,Emacs 有更直观的界面。您可以使用箭头键在整个文档中移动,当您按下一个键时,您可以期望它出现在光标碰巧出现的任何地方。像复制/粘贴、保存等特殊命令是通过按 Control 键调用的,后面是一系列其他命令,通常以“x”开头。例如,如果您想保存当前文档,您可以按 Ctrl-x,然后按 Ctrl-s,在 emacs 菜单中突出显示为 C-x C-s。

另一方面,Nano 比其他两个更直观。您可以像在任何其他编辑器中一样输入文本,并且您使用的命令总是显示在屏幕的底部。

如果你想尝试其中一个或者全部三个(在你下定决心之前总是一个好主意),确保你已经安装了所有的软件。为此,首先输入

sudo apt-get install emacs

sudo apt-get install vim

Nano 应该预装在 Pi 上;然而,emacs 和 Vim 不是。请注意,emacs 是一个相当大的下载,所以安装它和它的依赖项可能需要一些时间。去喝杯咖啡或者吃顿饭,当你回来的时候,它应该已经在等你了。

使用 Vim

正如我所说的,Vim 是一个模态编辑器,意味着您可以在插入和正常模式之间切换。要启动测试文件,请导航到您的桌面并键入

vim testfile.txt

Vim 不是打开另一个窗口,而是在终端中打开,如果您不习惯,这可能会让人感到困惑。您应该会看到一个类似于图 2-4 所示的窗口。

img/323064_2_En_2_Fig4_HTML.jpg

图 2-4

Vim 中的 Testfile.txt

Vim 以正常模式打开,这意味着您不能立即编辑该文件。为此,您必须通过键入“I”进入插入模式。单词“INSERT”将出现在左下角——这是一种提醒您处于插入模式还是正常模式的便捷方式。键入完毕后,按下 Esc 键返回正常模式。在普通模式下,您可以使用箭头键在文档中移动,就像在插入模式下一样,但是在您键入“I”之前,您不能更改或添加任何内容。若要保存文件,请至少按一次 Esc 键,确保您处于正常模式。然后,键入“:w”(不带引号),并按 Enter 键。要同时保存和退出,请键入“:x”(还是不带引号),然后按 Enter 键。显然,如果您在输入这些字符时处于插入模式,那么您所能做的就是将:w 或:x 添加到文档中。

Vim 需要很长时间来适应,许多人很难适应这两种不同的操作模式。如果你决定喜欢它,网上有很多教程教你如何充分发挥它的潜力。

使用 emacs

Emacs(至少对我来说)比 Vim 更直观一些,尤其是当您第一次开始使用它的时候。首先,打开一个终端并导航到您想要测试文件的位置,比如桌面。在那里,输入

emacs testfile.txt

Emacs 会寻找testfile.txt,存在就打开,不存在就创建打开。然后你会看到一个空白页,如图 2-5 所示。

img/323064_2_En_2_Fig5_HTML.jpg

图 2-5

emacs 中的 Testfile.txt

你可以立即开始打字。表 2-3 列出了 emacs 中最常见的命令。

表 2-3

emacs 中的常用命令

|

命令

|

击键

|
| --- | --- |
| 打开/新建 | Ctrl+x + Ctrl+f |
| 关闭 | Ctrl+x + Ctrl+c |
| 救援 | Ctrl+x + Ctrl+s |
| 切口 | Ctrl+w 组合键 |
| 复制 | Alt+w* |
| 粘贴 | Ctrl+y |
| 跳到行首 | Ctrl+a |
| 跳到行尾 | Ctrl+e 组合键 |
| 开始/结束选择 | ctrl+空格键 |

** Alt 键在大多数键盘上被定义为 Escape 键*

因此,举例来说,如果你想移动一行文本,把你的光标移到行首。按 Ctrl 和空格键;窗口左下角的状态文本将显示“标记已激活”然后,使用 Ctrl 和“e”将光标移动到行尾。状态文本将消失。现在,通过按 Ctrl+w 剪切选定的文本,将光标移动到要粘贴它的位置,然后按 Ctrl+y。

这确实需要一些时间来适应,所以如果你决定喜欢 emacs,网上有很多教程可以帮助你学习击键。一旦你学会了它,它会变得非常强大,但是请永远记住:这些命令中的大部分(如果不是全部的话)都可以从菜单中访问。

使用纳米

如前所述,nano 可能是三个编辑器中最容易使用和习惯的。要在 nano 中启动文件,只需输入

nano testfile.txt

进入您的终端,您应该会看到如图 2-6 所示的屏幕。与其他两个编辑器一样,如果指定的文件存在,nano 将打开它;否则,nano 会为您创建它。

img/323064_2_En_2_Fig6_HTML.jpg

图 2-6

nano 中的 Testfile.txt

如您所见,常用命令列在底部,脱字符(^)表示 Ctrl 键。要保存文件,请键入 Ctrl+X 退出。系统会询问您是否要保存该文件以及以什么名称保存。一般来说,键入“Y ”,然后按回车键保存您打开或创建的文件。

默认文本编辑器

Pi 曾经有一个名为 Leafpad 的编辑器,但现在它只是一个功能齐全但没有名字的轻量级编辑器,类似于 Ubuntu 的 gedit,Mac 的 TextEdit 或 Windows 的 TextPad。要打开它,点击 Pi 桌面左上方的树莓图标,然后选择“文本编辑器”(图 2-7 )。

img/323064_2_En_2_Fig7_HTML.jpg

图 2-7

打开默认文本编辑器

正如您将看到的,看起来您所熟悉的大多数编辑器。如果你习惯使用它,请使用它。我没有提到它太多,因为它的一个主要缺点是,它只有在 Pi 的图形桌面上工作时才可用。如果您远程登录到 Pi,并且只通过命令行工作,那么文本编辑器是不可访问的。

摘要

您对 Linux 的介绍到此结束。虽然这并不能让你成为专家,但它应该让你对这个强大的操作系统所能做的一切有一个健康的认识。您学习了如何仅使用命令行浏览文件系统的基础知识,并了解了 shell。还向您介绍了可用的文本编辑器,并且希望您已经选择了一个您熟悉的编辑器。一旦您在您的 Pi 上对它有了足够的了解,您可能会发现自己正在一台或多台其他机器上安装 Linux。没关系,我不会告诉任何人。

在下一章,我将尽我所能给你一个坚实的 Python 介绍。

三、Python 简介

你可能记得从第一章开始,Raspberry Pi 的创造背后的动力是让编程对每个人来说更容易理解,尤其是孩子。为此,创造者希望发布一款相对强大的计算机,它不会花很多钱,任何人都可以简单地连接到键盘、鼠标和显示器并开始编程。

这一创造的另一个方面是使编程更容易,因此 Eben Upton 和他的同伴决定将 Python 作为 Pi 操作系统的一个组成部分。他们认为,Python 是一种强大的语言,但它足够简单,没有任何编程经验的人也能很快掌握。

在这一章中,我将向您简要介绍 Python,带您完成创建几个脚本、运行它们的过程,并在此过程中学习这种强大语言的一些基础知识。我假设你至少对什么是 Python 有一点点了解,也许对编程有一点点了解,但仅此而已,因为——让我们面对现实吧——这就是你买这本书的原因。

脚本与编程语言

Python 是一种脚本语言。有些人可能会对它是编程语言还是脚本语言吹毛求疵,但是为了让严格的技术专家满意,我们称它为脚本语言。

脚本语言在几个方面不同于真正的编程语言。当您阅读以下比较时,请注意斜体部分:

  • 编程语言是编译的,不像脚本语言。像 C、C++和 Java 这样的通用语言必须由编译器编译。编译过程会产生一个机器代码文件,人类无法读取,但计算机可以读取并遵循。当你用 C 写一个程序并编译它时。o 文件是计算机读取的内容。这样做的副作用/结果之一是编程语言可能产生更快的程序,这既是因为编译只发生一次,也是因为编译器经常在编译过程中优化代码,使其比最初编写时更快。

    另一方面,脚本语言在每次运行时都会被读取、解释和执行。它们不产生编译的文件,并且完全按照编写的指令执行。如果你写草率的代码,你会得到草率的结果。出于这个原因,脚本语言导致程序变慢。

  • 编程/编译语言通常直接运行在编写它们的硬件之上。当你用 C++编写和编译一个程序时,产生的代码直接由你的台式机上的处理器执行。

    脚本语言通常在另一个程序“内部”运行,这个程序负责刚才提到的编译步骤。PHP 是一种常见的脚本语言,运行在 PHP 脚本引擎内部。Bash 脚本在 bash shell 中运行,这在前一章中已经介绍过了。

  • 编程语言往往更复杂,更难学。脚本语言可读性更强,语法要求更少,对非程序员来说也不那么可怕。

    仅仅因为这个原因,脚本语言经常在学校的入门编程课程中教授,直到学生掌握了编程的基础知识,才会向他们介绍更严格的语言,如 C 或 Java。

然而,两者之间的界限在过去的几年里变得如此模糊,以至于两者之间的区别几乎完全消失了。列举如下:

  • 虽然严格的编程语言是编译的,而脚本语言不是,但当今计算机中处理器速度和内存管理的进步几乎使编译语言的速度优势过时了。一个用 C 语言编写的程序和一个用 Python 编写的程序可能都执行相同的任务,而速度上的差异几乎可以忽略不计。某些任务可能确实更快,但不是全部。

  • 是的,脚本语言运行在另一个程序中。然而,Java 被认为是一种“真正的”编程语言,因为它必须在运行时进行编译,但它是在每个设备上的 Java 虚拟机内部运行的。事实上,这就是 Java 如此可移植的原因:代码是可转移的,只要某个版本的虚拟机运行在您的特定设备上。C#也是一种编译语言,但是它运行在另一种编程环境中。

  • 好吧,我真的不能否认这样一个事实:编程语言往往更复杂更难学,脚本语言的确更容易阅读和学习,语法规则更少,上下文更像英语。以下面两种打印“Hello,world!”的方式为例对着屏幕。**

    在 C++中,您应该写:

    #include <iostream>
    int main()
    {
    std::cout << "Hello, world!" << std::endl;
    return 0;
    }
    
    

    在 Python 中,您可以编写:

    print "Hello, world!"
    
    

    当然也有例外;我见过几乎难以辨认的 Python 脚本。同样,也有一些非常可读的 C 程序在流传。但是,总的来说,脚本对于程序员新手来说更容易学习,并且它们可以像编译的代码一样强大。

    是的,你可以用 C,C++,甚至 Java,或者(如果你特别喜欢)汇编语言来编写 Pi。但是现在你已经知道并看到了编程和脚本语言之间的区别,难道你不想使用 Python 吗?

    使用 Python 对 Pi 编程意味着,许多从未梦想过对计算机编程的人可以拿起一个 Raspberry Pi,用它做一些非常酷的事情,比如构建本书中介绍的一个项目,而无需学习一门困难的语言。毕竟,这就是 Pi 存在的原因——让更多的学生可以使用编程——因此 Python 被预装在 Pi 上。

Python 哲学

在脚本语言的世界中,Python 相对来说是一个新人,尽管它并不像许多人认为的那样新。它是在 20 世纪 80 年代后期开发的,可能比 Unix 的概念晚了 15 年。

其主要作者 Guido Van Rossum 于 1989 年 12 月实施了该计划。他一直积极参与 Python 的开发和进步,他对这种语言的贡献得到了 Python 社区的奖励,社区授予他终身慈善独裁者(BDFL)的称号。

Python 的哲学一直是让代码可读和可访问。Python 的“PEP 20(Python 的禅)”文档总结了这一理念,内容如下:

  • 漂亮总比难看好。

  • 显性比隐性好。

  • 简单比复杂好。

  • 复杂总比复杂好。

  • 扁平的比嵌套的好。

  • 疏比密好。

  • 可读性很重要。

  • 特例不足以特殊到打破规则。

  • 虽然实用性战胜了纯粹性。

  • 错误永远不应该无声无息地过去。

  • 除非明确沉默。

  • 面对暧昧,忍住猜测的诱惑。

  • 应该有一种——最好只有一种——显而易见的方法来做这件事。

  • 尽管这种方式一开始可能并不明显,除非你是荷兰人。

  • 现在总比没有好。

  • 虽然永远也不会比现在的好。

  • 如果实现很难解释,这是一个坏主意。

  • 如果实现很容易解释,这可能是一个好主意。

  • 名称空间是一个非常棒的想法——让我们多做一些吧!

除了这些戒律之外,Python 还有一个“包含电池”的思维模式,这意味着无论你需要在 Python 中做什么奇怪的任务,很有可能已经有一个模块可以完成,所以你不必重新发明轮子。

Python 入门

我们开始吧。在您的 Pi 上运行 Python 有三种方式:使用内置的解释器空闲、在终端窗口中或者作为脚本。我们将从使用 IDLE 开始。

使用 IDLE 运行 Python

空闲解释器是一种“沙箱”,在这里你可以与 Python 交互地工作,而不必编写整个脚本来查看它们做了什么。IDLE 这个名字代表“集成开发环境”,但它也向英国喜剧团体 Monty Python 的创始人之一埃里克·艾多尔致敬。(见侧栏“给我一个灌木林!”)

因为这是对用户最友好的测试代码的方式,所以让我们先使用 IDLE。点击菜单栏左上方的树莓图标,然后打开编程子菜单就可以找到了(见图 3-1 )。选择 Python 2(空闲)选项,因为我们将在 Python 2 中进行所有的 Python 编程(参见侧栏)。你应该会看到如图 3-2 所示的窗口。

img/323064_2_En_3_Fig2_HTML.jpg

图 3-2

空闲窗口

img/323064_2_En_3_Fig1_HTML.jpg

图 3-1

查找空闲接口

为了遵循伟大的编程传统,让我们从程序员用任何语言编写的第一个程序开始。在提示符下,键入

>>> print "Hello, world!"

然后按回车键。你应该立即受到问候

Hello, world!

这是 Python 的print语句,默认选项是到屏幕。现在打字

>>> x=4

然后按回车键。提示符将会返回,但没有任何反应。实际发生的是 Python 的解释器现在已经将x4关联起来。如果你现在打字

>>> x

迎接你的将是

4

同样,如果你输入

>>> print x

你会再次受到欢迎

4

这说明了 Python 另一个很酷的方面:动态类型化。在像 C 这样的语言中,必须在声明变量之前定义变量的类型,如下所示:

string x = "This is a string.";

或者

int x = 5;

注意

有关字符串的更多信息,请参阅本章后面的“字符串”一节。

当你告诉 Pythonx = 4的时候,python“知道”了x是一个int(整数)。

尽管是动态类型的,Python 是强类型的。这意味着它会抛出一个错误,而不是允许你做一些事情,比如给一个string添加一个int。您也可以使用类定义自己的类型;Python 完全支持面向对象编程(OOP)。我稍后会谈到这一点,但简而言之,这意味着您可以创建一个可能是整数、字符串和其他类型混合的对象,并且该对象将是它自己的类型。Python 有几种内置的数据类型:数字、字符串、列表、字典、元组、文件和其他一些类型(比如布尔)。我们将在本章的后面简要地介绍每一个。

接下来,让我们尝试在 IDLE 中使用一些变量和操作。打字

>>> print x+5

会返回9;然而,打字

>>> x + "dad"

会导致错误。然而,在这种情况下,打字

>>> "DAD" + "hello"

给你

'Dadhello'

因为对于 Python 来说,添加字符串等同于连接字符串。如果你想列一个清单,用方括号把它括起来:

>>> y = ['rest', 1234, 'sleep']

此外,字典(一种由相关的键和键值组成的文件)必须用花括号括起来:

>>> z = {'food' : 'spam', 'taste' : 'yum'}

注意

键和键值是 Python 字典不可或缺的一部分。它们只是相互关联的价值对。例如,在前面代码示例中的z字典中,'food''spam'分别是一个键和一个键值。同样,'taste''yum'是一个键和一个键值。要使用字典,需要输入它的键,然后返回相关的键值。

给我一根灌木!

Python 是而不是以蛇命名的;更确切地说,它的创造者范·罗森以 BBC 喜剧团的巨蟒剧团命名,他是该剧团的超级粉丝。因此,语言中大量引用了 Monty Python。在其他程序中用来说明代码的传统“foo”和“bar”在 Python 示例中变成了“spam”和“eggs”。如果你是 Monty Python 的粉丝,你会看到“Brian”、“ni”和“bushebry”的引用,所有这些对你来说都很有意义。就连翻译 IDLE 也是以议员埃里克·艾多尔的名字命名的。如果你不熟悉他们的作品,我劝你放下这本书,去看一些他们的草图。我强烈推荐《死鹦鹉小品》和《傻瓜式行走部》。学习这门语言不一定要熟悉他们的作品,但这可能有助于增加你的乐趣。

Python 2 对 Python 3

Python 在编程语言中有些独特,因为它有两个当前可用的受支持版本:版本 2 和版本 3。前者是 2.7.14 版本,Python 3 目前是 3.6.4 版本。Python 3 发布于 2008 年;2.7 是在 2010 年发布的,不会有更多的主要版本(即,不会有 2.8 版本)。Python 的创造者决定用第 3 版“清理”这种语言,对向后兼容性的考虑比你预期的要少。

最大的改进是 Python 3 处理 Unicode 的方式,该语言的几个方面对初学者来说更加用户友好。不幸的是,由于向后兼容性有限,有许多用 Python 2 编写的 Python 软件如果不进行一些重大的修改就无法在 Python 3 中运行,坦率地说,这并不是很有吸引力,尤其是因为 Python 2 仍然受支持。

我的建议是,集中精力学习 Python 2,因为 3 并没有太大的不同。在这本书里,我将使用 Python 2,但是如果你觉得有必要的话,可以随意翻译成 Python 3。

使用终端运行 Python

让我们快速访问一下 Python 的另一种使用方式,那就是使用终端。打开 Pi 桌面上的终端,在提示符下键入python。你会看到与打开空闲窗口相同的介绍性文本和相同的交互式>>>提示。此时,您可以发出与上一节“使用 IDLE 运行 Python”中讨论的相同的命令,并获得相同的结果。

使用脚本运行 Python

IDLE 和 terminal 的问题都是你写不出真正的脚本。一旦你关闭了窗口,你声明的所有变量都消失了,并且没有办法保存你的工作。在文本编辑器中编写 Python 的最后一种方法解决了这个问题。你可以写一个完整的程序,用一个.py扩展名保存,然后从终端运行它。

让我们使用 Pi 的默认文本编辑器编写一个非常短的脚本。从附件菜单中打开它(图 3-3 )。

img/323064_2_En_3_Fig3_HTML.jpg

图 3-3

访问文本编辑器

在出现的窗口中,键入以下内容:

x = 4
y = x + 2
print y

将其保存到您的桌面上作为test.py。现在,打开终端,通过键入以下命令导航到您的桌面

cd ~/Desktop

您现在可以通过键入以下命令来运行您的脚本

python test.py

你应该会看到号码6。恭喜你!您刚刚编写、保存并运行了您的第一个 Python 脚本!

当您编写本书中的脚本时,可以随意使用任何文本编辑器。如果您习惯使用默认的文本编辑器,那就使用它吧。我倾向于使用基于终端的编辑器 nano 或 emacs,因为我经常远程登录到我的 Pi,并且默认编辑器不能在远程登录会话中运行。出于这个原因,我经常告诉你像这样编辑文件:

sudo nano spam-and-eggs.py

你想用哪个编辑器就用哪个编辑器。

接下来,让我们简单地看一下每种数据类型,看看您可以对每种类型做些什么。

探索 Python 数据类型

如前所述,Python 为您提供了几种内置的数据类型。在下面几节中,您将了解数字、字符串、列表、字典、元组和文件。

民数记

数字似乎是不言自明的,事实上,如果您有编程经验,您会认识到 Python 的数字类型:整数、短整型、长整型、浮点型等等。Python 有表达式运算符,允许您对此类数字进行计算;这些包括+-/, *%;比较运算符,如>>=!=orand;和许多其他人。

所有这些操作符都是内置的,但是您可以通过使用 Python 的另一个伟大特性来导入其他操作符:导入模块。模块是额外的库,可以导入到脚本中,增加 Python 的本地功能。在这方面,Python 很像 Java:如果你想做某件事,很有可能有一个库的存在使它变得更容易。例如,如果您想解析文本,如网页,您可以查看漂亮的 Soup 模块。需要登录到一台远程计算机上(对于某些项目,您会这样做)?导入 telnetlib 模块,需要的都有了。对于数字,math 模块有各种数学函数,增加了 Python 的数字功能。你可以自己试试。在空闲会话中,键入

>>> abs(-16)

并且你应该得到结果16。这是因为绝对值函数(我将在本章后面的独立章节中讨论函数的主题)已经包含在 Python 的默认库中。然而,打字

>>> ceil(16.7)

将返回一个错误,因为在那些默认库中,上限函数是而不是。肯定是进口的。现在打字

>>> import math
>>> math.ceil(16.7)

终端将返回17.0—x 的上限,或者大于 x 的最小整数。虽然您可能不需要使用ceiling函数,但简单地导入math模块将为您提供各种额外的功能,如对数和三角函数以及角度转换,所有这些都只需要一行代码。

用线串

在 Python 中,字符串被定义为用于表示基于文本的信息的字符的有序集合。Python 不像 C 和其他语言那样有char类型;单个字符就是单个字符的字符串。字符串可以包含任何可视的文本:字母、数字、标点符号、程序名等等。当然,这意味着

>>> x = 4

>>> x = "4"

不是一回事吗?您可以将3添加到第一个示例x中,但是如果您尝试使用第二个示例,Python 将会给出一个错误— x指向一个值为4的字符串,而不是一个整数,因为4被括在引号中。Python 不区分单引号和双引号;您可以在任一个中包含一个字符串,它将被识别为一个字符串。这有一个很好的副作用:您可以在字符串中包含另一种类型的引号字符,而不必像在 c 语言中那样用反斜杠进行转义。例如:

>>> "Brian's"

给你

"Brian's"

不需要任何转义字符。

在您的 Python 生涯中,有一些基本的字符串操作您可能会用到很多次,比如len(字符串的长度)、连接、迭代、索引和切片(Python 的 substring 操作的等价物)。举例来说,在空闲会话中键入以下代码,注意结果与您在这里看到的输出相匹配:

>>> len('shrubbery')

9

'shrubbery' is 9 characters long.

>>> 'spam' + 'and' + 'eggs'

'spam and eggs'

'spam', 'and', and 'eggs' are concatenated.

>>> title = "Meaning of Life"
>>> for c in title: print c,
(hit Enter twice)

M e a n i n g  o f  L i f e

对于'title'中的每个字符,打印出来。(注意print c后的逗号;它告诉解释器一个接一个地打印字符,而不是按向下的列打印。)

>>> s = "spam"
>>> s[0], s[2]
('s', 'a')

spam”的第一([0])和第三([2])字符是“s”和“”。

>>> s[1:3]
'pa'

第二个第四个字符是“pa”。(在命名字符串或其他数组中的一系列字符时,第一个参数是包含性的,第二个不是。)

当您有一个当前类型为 string 的整数(如"4")并且您想要执行一个操作(如求平方)时,您还可以在 string 对象和 string 对象之间进行转换。要做到这一点,就像打字一样简单

>>> int("4") ** 2
16

只要使用 Python 的内置字符串库,您就可以在 ASCII 代码之间进行转换,使用像%d 和%s 这样的转义字符进行格式化,从大写转换为小写,以及一系列其他操作。

列表

列表和字典可以说是 Python 内置数据类型中最强大的。它们实际上是其他数据类型的集合,非常灵活。它们可以根据需要改变位置、增长和收缩,以及包含和被包含在其他种类的对象中。

如果您有使用其他编程语言的经验,您可能会认为 Python 列表相当于 C 语言中的指针数组,事实上,列表实际上是 Python 解释器中的 C 语言数组。因此,它们可以是任何其他类型对象的集合,因为它们包含的指针对象可以指向任何其他数据类型,包括其他列表。它们也是可索引的——和索引 C 数组一样快。它们可以像 C++和 C#的列表一样伸缩;它们可以被分割、切块和连接——几乎任何你对字符串做的事情,你都可以对列表做。

要创建一个列表,用方括号([])声明它,如下所示:

>>> l = [1, 2, 3, 4, 5]

或者

>>> shrubbery = ["spam", 1, 2, 3, "56"]

声明它们之后,你可以用它们玩各种游戏,比如连接等等:

>>> l + shrubbery
[1, 2, 3, 4, 5, 'spam', 1, 2, 3, '56']

>>> len(shrubbery)
5

>>> for x in l: print x,
...
1 2 3 4 5

>>> shrubbery[3]
3

(您可能还注意到,列表和数组一样,是从 0 开始索引的。)通过使用索引和切片操作,您可以通过组合删除和插入来就地更改列表:

>>> cast = ["John", "Eric", "Terry", "Graham", "Michael"]
>>> cast[0:2] = ["Cleese", "Idle", "Gilliam"]
>>> cast
['Cleese', 'Idle', 'Gilliam', 'Graham', 'Michael']

列表还允许你使用与它们相关的特定函数调用,比如appendsortreversepop。更新名单(没有双关语!list的功能,键入

>>> help(list)

注意

Python 的help函数极其有用。如果你不知道如何做某事,或者有什么可用的,或者如何使用一个特定的功能,在提示符下键入help(<confusing object>)可以给你很大的帮助。(参见侧栏“Python 帮助”)

Python 帮助

如果您曾经沉迷于 Python,它的在线文档是非常有用的资源。将您的浏览器指向 http://docs.python.org/2/library/stdtypes.html 以阅读所有可用的标准数据类型以及如何使用它们。同样, http://docs.python.org/2/library/functions.html 会显示所有可供您使用的功能。它内置的帮助功能也很彻底。要尝试它,在空闲会话类型中

>>> import string

然后

>>> help(string)

你会得到你想知道的关于弦的一切。同样,打字

>>> help(string.capitalize)

将向您展示如何使用大写功能。

字典

像列表一样,Python 字典是极其灵活的对象集合。字典的不同之处在于,不像列表,它们是无序的;你可以通过索引来访问列表中的条目,但是字典中的条目是通过键来访问的。换句话说,字典包含键值对;请求该键将返回与该键相关联的值。例如,在下面的字典中,值'spam'可以通过它的键']'来访问:

>>> dict = {'food': 'spam', 'drink': 'beer'}
>>> dict['food']
'spam'

像列表一样,字典可以嵌套:

>>> dict2 = {'food': {'ham': 1, 'eggs': 2}}

这意味着键'food'有一个关联的键值{'ham': 1, 'eggs': 2},它本身就是一个字典。

字典有特定的方法调用:

>>> dict.keys()
['food', 'drink']

这列出了dict中的所有键。

>>> dict.has_key('food')
True

如果dict包含关键字“food”,则返回True,否则返回False

字典可以就地更改:

>>> dict['food'] = ['eggs']
>>> dict
{'food': ['eggs'], 'drink': 'beer'}

这将把'food'的键值从'spam'更改为'eggs'。(在这里,你会注意到']'除了是一个普通的项目,也是一个单项式列表。)

注意

正如您在这里看到的,键并不总是字符串。你可以使用任何不可变的对象作为键;如果您碰巧使用整数,字典的行为更像一个列表——也就是说,是可索引的(通过整数键)。

元组和文件

这里我要提到的最后一种主要数据类型是元组和文件。元组是不能更改的其他对象的集合,文件是指计算机上文件对象的接口。

元组是对象的有序集合。它们很像列表,但是不像列表,它们不能在适当的位置改变,并且用圆括号写,而不是方括号,像这样:

>>> t = (0, 'words', 23, [1, 2, 3])

这里,t包含两个整数、一个字符串和一个列表。您可以嵌套元组、索引它们、分割它们,以及做几乎任何您可以用列表做的事情,除了就地改变它们。

那么,如果元组几乎和列表一模一样,为什么还会有元组呢?最普遍接受的答案是,因为它们是不变的——它们不能被改变。通过将对象集合声明为元组而不是列表,可以确保该集合不会在程序的其他地方被更改。这有点像在 C 语言中把某个东西声明为const——如果你以后试图改变它,编译器会给你一个错误。

回想一下,我在第二章中谈到了文件,所以这个概念你应该很熟悉。Python 有一个内置函数open,它创建一个文件对象,该文件对象链接到计算机内存中的一个文件。文件对象与其他类型有点不同,因为它们实际上只不过是可以在那些外部文件上调用的函数的集合。这些函数包括readwriteopenclose,以及对文本文件的各种解析函数。举例来说,下面几行打开一个文件test.txt(如果它不存在,则创建它)进行写入,向其中写入一行文本(以换行符结束),然后关闭该文件:

>>> myfile = open('test.txt', 'w')
>>> myfile.write('Hello there, text file!\n')
>>> myfile.close()

当您执行命令时,所有这些都发生在您所在的目录中。

然而,请注意,如前所述,如果test.txt已经存在,其内容将被myfile.write()调用覆盖。如果您想要附加到文件而不是覆盖它,请在打开文件时使用“a标志,而不是“w”。

一旦打开了一个文件,您就可以读取和写入它,记住您只能从 file 对象中读取 string 对象。这仅仅意味着,在对文件中的所有对象执行任何操作之前,必须将它们转换成“真正的”数据类型;如果myfile.readline()返回“456”,如果要对其进行计算,必须用int()将 456 转换成整数。

文件操作非常有用,因为它们允许你创建和写入文本文件,但是它们超出了本章介绍的范围。当我们在项目中使用它们时,我们将在后面重新讨论它们。

正如您所看到的,Python 的内置数据类型可以做“真正的”编程语言可以做的任何事情——有时更容易也更经济。通过组合这些类型,您可以用 Python 完成一些真正强大的过程,接下来您将看到这一点。

用 Python 编程

现在您已经看到了数据类型,让我们研究一下如何在实际程序中使用它们。要创建 Python 程序,必须退出解释器并打开文本编辑器,如 emacs 或 Pi 的 Leafpad。创建程序后,用扩展名.py保存它。然后,您可以通过键入以下命令来运行它

$ python myprogram.py

Python 在语法上是编程语言中独一无二的,因为它使用空白或缩进块来屏蔽代码。像 C 这样的语言用花括号括起了一个代码块,比如一个if语句;Python 使用冒号和缩进来描述块。

C 语言中的代码如下所示:

if (x==4)
{
    printf("x is equal to four\n");
    printf("Nothing more to do here.\n");
}
printf("The if statement is now over.\n");

Python 中的相同代码如下所示:

if x == 4:
    print "x is equal to four"
    print "Nothing more to do here."
print "The if statement is  now over."

您可能会注意到关于 Python 程序的两个附加细节。首先,if语句中的括号是不必要的。在 Python 中,括号是可选的,但在大多数情况下,使用括号被认为是良好的编程实践,因为它增强了代码的可读性。您还会注意到,大多数其他语言都以分号结束它们的代码行;Python 没有。这可能需要一些时间来适应,但这是一个很好的改变,不会因为某个地方的分号放错位置或丢失而导致程序编译失败。在 Python 中,行尾就是语句的结尾——就这么简单。

您已经看到了一些语句,例如

x = 4
y = "This is a string."

如前所述,Python 不需要声明来告诉它x是一个整数而y是一个字符串——它只需要知道。这些语句被称为赋值,其中右边的值被赋给左边的变量。不同的语言有不同的变量命名约定,但是我能给你的最好的建议是选择一个约定并坚持下去。如果你更喜欢帕斯卡格(ThisIsAVariable),就用它;如果你喜欢驼背(thisIsAVariable),就用那个。保持一致——你以后会感谢自己的。在任何情况下,赋值就是这样做的:给一个变量赋值,不管这个变量是数字、字符串、列表还是其他什么。这是最简单的编程功能。

IF 测试

我们要看的下一个编程功能是if语句和它的派生物——elifelse。正如您所料,if执行一个测试,然后根据测试结果从备选方案中进行选择。最基本的if语句是这样的:

>>> if 1:
... print 'true'
...
true

1与布尔值true相同,所以前面的语句将总是打印true

注意

当您在终端(或空闲状态)的 Python 提示符下键入if语句并以冒号结尾时,下一个提示符将总是省略号(...),这意味着 Python 需要一个缩进的块。如果你完成了缩进块,只需再次按回车键结束它。如果你在文本编辑器中写程序,确保你缩进了你需要缩进的块。

从现在开始,我将格式化代码,就像它在文本编辑器中一样,并打印输出,就像您运行了脚本一样。

更复杂的测试使用elifelse,如下所示:

x = 'spam'
if x == 'eggs':
    print "eggs are better when they're green!"
elif x == 'ham':
    print 'this little piggy stayed home."
else:
    print "Spam is a wonderful thing!"

很明显,这段代码输出的是“垃圾邮件是个奇妙的东西!”当程序执行时,计算机检查第一个if。如果该语句被确定为真,它就直接执行其后的缩进块。如果该语句为 false,它将跳过缩进的块并查找一个elif,然后对其求值。同样,如果确定为真,或者没有elif,计算机执行下面的块;如果没有,它跳过那个块,寻找另一个elif或一个else

这里有三点很重要,值得一提。首先,请记住,如果一个if语句被确定为假,那么下面缩进的块中的不会被执行——计算机直接跳到下一个未缩进的行。

其次,Python 和其他语言一样,使用双等号来表示对相等性的测试。单个等号用于赋值;替身是一种考验。我提到这一点是因为每个程序员——我指的是每一个程序员——都在某个时候在一个if语句中使用了一个等号,结果他们的程序做了各种奇怪的、意想不到的事情。你也会这么做的,但我希望至少能提前给你省下一点恼怒。

第三,Python 忽略空行和空格(当然,除了交互提示和缩进块)以及注释。这一点很重要,因为它让你可以确保你的代码对其他程序员来说是可读的,即使那个程序员是你。

注意

Python 中的注释前面有一个#;程序忽略该行后面的任何内容。

代码的可读性非常重要;希望我定期向你灌输这一点。您更愿意尝试调试前面的程序还是类似这样的程序:

x='this is a test'
if x=='this is not a test':
    print"This is not "+x+" nor is it a test"
    print 89*2/34+5
else:
    print x+" and I'm glad "+x+str(345*43/2)
print"there are very few spaces in this program"

虽然你肯定可以读第二个,但这并不有趣,在没有空格、空行或注释的数百行代码之后,你的眼睛会感谢你——相信我。如果使用空格,请注意倒数第二行的不同之处:

print x + " and I'm glad " + x + str(345 * 43 / 2)

你可以使用空格,所以请随意使用!

我想提到的if语句的最后一部分是布尔运算符。在真值测试中,如果 X 和 Y 都为真,则 X and Y 为真。如果 X 或 Y 为真,X or Y 为真,如果 X 为假,Xnot为真。Python 使用单词,而不是 C 或 C++的&&||!操作符。学习这些运算符;它们会很有用的。

通常,一个程序从上到下执行,一次一行。但是,某些语句会导致程序执行到处乱跳;这些控制流语句包括if / then和循环。

最简单的循环可能是执行固定次数的代码块,例如

for x in range (0, 10):
    print "hello"

这只是打印

hello
hello
hello
hello
hello
hello
hello
hello
hello
hello

您还可以使用for循环来遍历可迭代的项,比如一个字符串甚至一个列表:

for x in "Camelot":
    print "Ni!"

Ni!
Ni!
Ni!
Ni!
Ni!
Ni!
Ni!

或者,迭代并打印字符本身:

for x in "Camelot":
    print x

C
a
m
e
l
o
t

虽然for循环的语法与 C 或 Java 有点不同,但是一旦你习惯了,使用语法就成了你的第二天性。

另一个循环语句是while语句。该语句计算一个条件,只要该语句为真,就继续执行缩进块:

x = 0
while (x < 10):
    print x
    x = x + 1

0
1
2
3
4
5
6
7
8
9

与您预期的不同,这段代码不会打印“10”,因为 x 在打印后会递增。在第十次迭代时,解释器打印“9 ”,然后将x增加到 10。此时,while条件不再为真,所以块内的代码永远不会被执行。

如果你在等待某个特定事件的发生,比如按键或用户按“Q”退出,语句会很有用。这方面的一个例子如下:

while True:
    var = raw_input("Enter something, or 'q' to quit):
    print var
    if var == 'q':
        break

关于这个脚本有两个细节需要注意:首先,在 Python 2.x 中,命令raw_input用于从用户那里获取输入。在 Python 3.x 中,这个命令被简单地改成了input。第二,记住break的命令。这个命令实际上打破了您碰巧所在的循环。所以,在这种情况下,循环的while部分使它永远运行下去,但是如果检查var == 'q'返回true,脚本就会跳出循环并结束程序。

功能

函数允许程序员重用代码,提高效率。一般来说,如果你发现你需要在你的代码中执行一个特定的任务超过两次,那么这个任务很可能是一个函数的候选者。

假设你写了一个简单的程序来计算一个矩形的面积和周长。它要求用户输入矩形的高度和宽度,然后执行必要的计算。最简单的方法之一是创建一个函数,将矩形的高度和宽度作为输入参数。然后打印矩形的面积和周长,并返回程序。为此,我们使用一个复合语句块,以def赋值开始。def赋值是我们如何定义一个函数,语法是def functionname (firstparameter, secondparameter):

def AreaPerimeter (height, width):
    height = int(height)
    width = int(width)
    area = height * width
    perimeter = (2 * height) + (2 * width)
    print "The area is:" + area
    print (The perimeter is:" + perimeter
    return

while True:
    h = raw_input("Enter height:")
    w = raw_input("Enter width:")
    AreaPerimeter (h, w)

这个小程序只是简单地接受你输入的数字并返回计算结果。虽然这可能不是最好的例子(你可以用更少的代码进行动态计算),但它说明了代码重用的思想。有了这个函数,无论你在程序中的什么地方需要计算面积或周长,你只需要用“height”和“width”两个参数调用AreaPerimeter,就为你完成了。

这里要注意一点:raw_input总是返回一个字符串,即使你输入了数字。这就是为什么在进行任何计算之前,AreaPerimeter中的heightwidth变量必须转换成int s。

如果您熟悉其他语言,Python 的函数与其他语言中的方法、函数和过程略有不同。首先,在 Python 中,所有函数都是引用调用。在不深入编程的情况下,这意味着当你传递一个参数给一个函数时,你实际上只传递了一个指向变量的指针,而不是变量本身。这有助于使 Python 对内存更加友好——例如,您不必随意地复制整个列表,并将它们来回传递给函数。相反,如果一个函数把一个列表作为参数,你把列表第一项的内存位置传递给它,它就根据这个位置和项做它需要做的事情。

函数的另一个有趣的方面是它们是可执行的语句。例如,这意味着函数定义实际上可以在一个if语句中声明和调用。虽然不正常,但这样做是合法的(有时是有用的)。def可以嵌套在循环、其他def甚至列表和字典中。

我们将在项目中再次访问函数;现在,请注意它们的存在,它们是你编写的任何程序中非常有用的部分。

对象和面向对象编程

在这篇 Python 简介中,我想说明的另一个重要事项是它运行面向对象代码的固有能力。虽然面向对象编程(OOP)可能是一个高级主题,并且可能超出了本书的范围,但我认为它是一个足够重要的主题,可以轻松地浏览一下。

OOP 是一种范例,其中程序数据被分成混合的对象和函数或方法。对象是一种数据类型——通常是其他数据类型的集合,如整数、字符串等等。对象通常是的一部分,这些类有关联的方法可以作用于该类的成员。

也许说明这一点最简单的方法是用一个使用形状的例子。在这个例子中,形状是一类对象。那个类有相关的值,比如namenumberOfSides。那个类也有相关的方法,比如findArea或者findPerimeter

shape类有子类,更具体。正方形是一个shape对象,其值shapeType等于square,numberOfSides等于4。它的findArea方法获取lengthOfSides值并求平方。同时,triangle对象对于nameshapeTypenumberOfSides有不同的值,其findArea方法也不同。

这个例子在快速介绍对象的同时,也说明了继承的概念,继承是 OOP 不可分割的一部分。三角形对象从其shape继承namenumberOfSidesfindArea部分(尽管这些部分有不同的值和实现)。如果一个对象从shape类继承,它也将继承那些部分。它不一定那些零件,但它有。它可能有额外的部分(例如,circle对象可能有一个radius值),但是它总是有那些部分。

如果你开始在编程中使用类,Python 比它的同类,如 C++或 Java,更容易理解。您几乎可以用语法object.attribute命名任何对象或方法,无论该属性是对象还是方法。如果你有一个名为holyGrailcircle物体,它的半径是holyGrail.radius。名为unexplodedScotsmansquare具有由unexplodedScotsman.findArea定义的区域。

就像我说的,OOP 超出了本书的范围。然而,像函数一样,它也非常有用,尤其是在更长、更复杂的程序中。随着您在 Python 研究方面的进步,您可以随时进行进一步的研究。您会发现 Python 也是一种非常通用的语言,甚至允许您执行函数和其他高级编程任务。

摘要

在这一章中,我给了你一个简短而实用的 Python 介绍,从它的一点历史开始,然后继续讲述如何与 Python 提示符交互,帮助你学习它的一些数据类型,然后向你展示一点使用编辑器编写脚本。如果你不能一下子理解所有这些信息,不要担心。这里有很多要学的东西,我会在我们进行书中的项目时解释我在做什么。与此同时,Python 上有数以千计的书籍和课程,所以如果你想了解更多,请随意在网上和当地的书店里寻找。

在下一章,我们将学习电子学 101。毕竟,你将会进行项目建设,在此之前,你应该对电、动力以及各种电子部件和小发明有一个基本的了解。

四、时速 100 英里的电子设备

您购买这本书是为了学习使用 Python 编程,并了解 Raspberry Pi。您还想构建一些很酷的项目,了解 Pi 如何运行 Linux,以及如何使用 Python 与 Pi 和各种附加组件进行交互。

我们会谈到这一点,但在此之前,我需要解释一些其他重要的先决条件,即电子和电气规则、工具、安全规则以及一些操作方法。它们可能不是最性感的话题,但任何涉及建筑电子项目的书都应该至少有一章涉及像欧姆定律和 ?? 如何焊接这样的概念,事实是,是的,?? 完全有可能用 9V 电池电死自己。(见侧栏。)更不用说,我不希望我的读者因为我没有做足够的安全指导而受到任何身体伤害。所以,如果这些信息对你来说是全新的,请至少浏览一下这一章并做些笔记。如果你读完这一章,觉得有必要穿得像图 4-1 那样来保护自己,那完全没问题。

img/323064_2_En_4_Fig1_HTML.jpg

图 4-1

可能的实验室安全装备

达尔文奖

如果你还不知道的话,达尔文奖是一个幽默的奖项,每年颁发给那些由于自己的愚蠢,通过死亡或绝育,成功将自己从基因库中移除的人类成员。过去的获奖者包括在从变电站偷铜线时触电身亡的小偷,在高速行驶时与乘客交换位置的司机,以及在静脉中注射罂粟籽的吸毒者。

9V 电池电死事件发生时,一名海军水手试图测量他的身体电阻,将他的 9V 万用表的尖头插入他的拇指,使他的血液成为完美的导体。电流穿过他的心脏,扰乱了他的心跳,杀死了他。

你可以在达尔文奖的网站上了解更多关于达尔文奖的信息: www.darwinawards.com

基本电学概念

。。。他说,他是这样说的:"只有一个定律,这个定律是欧姆定律,那就是 V 等于 I 乘以 r。"

好吧,我知道这个报价有些俗气;但是,欧姆定律确实是任何一个电气工程专业的学生学习的第一件事,它影响着你在电子学方面所做的一切。这意味着总电压(V;用伏特测量)等于电路中任何一点的电流(I;测量单位为安培)乘以电阻(R;以欧姆测量)。 I 代表电感,这就是为什么它是 I,而不是 c。因此,如果有一个 200 欧姆的电阻,有 0.045 安培的电流流过它,该电阻上的电压等于 9 伏。像任何好的代数方程一样,它是可以互换的:

  • V=I×R I=V**R R=V**I

电路中的另一个重要变量是功率,用 P 表示,单位是瓦特。功率等于电压乘以电流,电压的平方除以电阻,或者电流的平方乘以电阻。如果这令人困惑,使用图 4-2 中的图表来更好地想象这些关系。

img/323064_2_En_4_Fig2_HTML.jpg

图 4-2

常见电气方程

举例来说,说明不同电气概念的常见方式是用水和不同尺寸的管道。在“水回路”中,水力是由水泵提供的。在电路中,电源由电池提供。在水回路中,泵在低压下取水,增加压力,并将水沿着回路输送。在电路中,电池获得“低压”电压,增加电压,并在电路中传输。在这两种情况下,电流意味着电路周围的电子或水的流动。电路的电阻类似于水管有多大。如果管子很大,它对水流的阻力就较小。在电路中,如果导线的电阻较小,电子就可以更自由地流动。这反过来影响了的力量

功率随着电阻和电流的增加而增加。把功率想象成电的“速度”;如果有一定量的水从软管的一端流出,你把手指放在软管的一端,增加阻力,水的速度就会增加。增加电路的电阻会增加功率。当然,这也有副产品。部分堵塞软管末端会增加软管口处的摩擦,从而增加热量。同样,增加电路的电阻通常意味着增加热量。热量对电路不利,尤其是像集成电路(ic)这样的易碎物品,因此许多产生热量的电子元件(由于内阻等原因)通常内置有散热器来散发它们产生的热量。

从最基本的角度来说,电只不过是电子沿着电线或其他路径来回移动。该路径总是阻力最小的路径。给定两种旅行方式的选择,电子将总是选择最容易的路径,无论是通过导线、螺丝刀还是人类躯干。当与这些电子一起工作时,你的目标是确保最容易的路径不涉及你的身体。你不会永远成功;我被电击的次数都数不清了。(事实上,除了我经历过的电池和电源的多次电击之外,我实际上还被闪电击中过三次。)一个有良知的实验者应该努力减少这些事件,如果没有别的原因,仅仅是因为它们会伤害!橡胶手套会有帮助(尽管一直戴着有点不切实际),橡胶靴或橡胶底鞋也可以。橡胶靴之所以是个好主意,除了它们非常时尚之外,还因为电子总是想接地。该“地”可以是电源地,如电池的端子;底盘接地,就像汽车中的发动机组;或者实际接地,称为接地。你和地面之间的橡胶屏障阻止了这些电子通过你的身体。

了解了电学基础知识之后,让我们来谈谈构建项目所需的工具。

机器人所需的工具

所有的工程师都需要好的工具,作为一个初露头角的爱好者/实验者/工程师,你也不例外。放在你厨房垃圾抽屉里的那个凹陷的、破损的螺丝刀可能可以用来撬墙钉,但如果你试图用它来做你项目中的任何精细工作,你只是在自找麻烦。同样,当你试图在计时器到达零点之前伸进一个小孔去剪断红线时,一把齿缝很大的钢丝钳不会给你带来任何好处。要做酷的东西,你需要好的工具。以下部分将描述您应该拥有的必要工具。

螺丝刀

你需要一套好的小型珠宝商螺丝刀。多花 10 美元,买一台质量好、能用很长时间的电视机,最好是由淬火钢制成的。该套件应至少有三把普通螺丝刀和三把十字螺丝刀,普通尺寸从 3/64 英寸到 1/8 英寸,十字尺寸包括#0 和#1。一把好的螺丝刀价值相当于黄金,因为它不太可能剥开螺丝或出现损坏的尖端,根本抓不住螺丝。

此外,确保你手头有一把标准尺寸的普通螺丝刀和一把 2 号十字螺丝刀,因为你很容易组装/拆卸普通尺寸的物品和微型物品。我建议你买一把带有不同刀头的棘轮螺丝刀,这样你就可以为大部分项目做好准备。

钳子和剥线钳

同样,把钱花在好的钳子和剥线钳上,因为物有所值。你肯定需要一把好的尖嘴钳(如图 4-3 所示),它可以被用作镊子或者用来弯曲零件。

img/323064_2_En_4_Fig3_HTML.jpg

图 4-3

尖嘴钳

没有普通的钳子,你可能也能逃脱,尽管我不建议你这么做。好好照顾你的钳子,它们也会好好照顾你。用一把有缝隙或不能正确闭合的钳子很难弯曲电线或切断触点。

你还需要一些剥线钳。是的,你可以用钳子上的钳子小心地刻划电线绝缘层并将其剥去,但是当你每次需要电线末端时都必须重复这样做,这很快就会变得乏味。找些剥线钳,省得你头疼。无论是图 4-4 所示的那种还是图 4-5 所示的那种都可以——只要确保你知道如何使用它们。

img/323064_2_En_4_Fig5_HTML.jpg

图 4-5

剥线器,版本 2

img/323064_2_En_4_Fig4_HTML.jpg

图 4-4

剥线钳,版本 1

我实际上两者都用,因为虽然我很高兴能够在图 4-4 所示的线对中选择我的线尺寸,但我经常遇到不适合该型号任何预定尺寸孔的奇数尺寸线。这就是图 4-5 所示的剥离器发挥作用的地方。这些工具使用起来要快得多,如果你一次剥开很多电线,你会体会到它的速度和方便。

钢丝钳

你需要两种类型的刀具:普通刀具(图 4-6 )和精细刀具(图 4-7 )。

img/323064_2_En_4_Fig7_HTML.jpg

图 4-7

细钢丝钳

img/323064_2_En_4_Fig6_HTML.jpg

图 4-6

剪钳

常规尺寸的刀具非常适合日常工作,但是当你必须剪断微小的断裂焊点或 24 号线的磨损末端时,较小的刀具是非常宝贵的。

文件

当涉及到锉刀时,你不需要任何花哨的东西,只需要一套具有不同切口或粗糙度的小锉刀。较细的切口可用于在焊接前使接头变粗糙,或者在将导线插入试验板之前从导线末端去除一点焊料,而较粗的切口可用于重塑金属和塑料外壳、增加孔的尺寸以及各种其他任务。

放大镜

你将与许多非常小的物体打交道,从电阻到电线到伺服连接,你的眼睛很快就会疲劳。一盏内置放大镜的可调节台灯是非常有价值的投资。我使用的是专为珠宝商和珠饰商设计的,当我试图处理微型作品时,它的作用是不言而喻的。(参见图 4-8 。)

img/323064_2_En_4_Fig8_HTML.jpg

图 4-8

放大镜

热胶枪

在某些情况下,你需要将一些东西粘到其他东西上,比如将一个伺服系统粘到另一个伺服系统上,或者将印刷电路板(PCB)粘到机器人的平台主体上,而使用螺钉或螺栓可能并不可行。最好的方法之一是用热胶枪。忽略剪贴簿和纸板/通心粉设计的耻辱,得到一个好的胶枪。热熔胶在各种应用中的效果令人惊讶——木材与塑料、塑料与塑料、木材与金属等等。

各种胶水

说到胶水,你可能会想买一种其他的非热胶枪胶水。强力胶是必备的(不过要买名牌的,不要买商店里的牌子),建模水泥也是。我还储备了五分钟环氧树脂和橡胶胶水,最近发现大猩猩胶水是迄今为止最棒的胶水之一。你可能还会发现这些冷焊棒的用途——将两种类似油灰的物质混合在一起,形成一种“粘土”,硬化成钢铁般的稠度。

将磁带也添加到该列表中;准备一些普通的透明胶带、双面胶、遮蔽胶带、绝缘胶带,当然还有管道胶带。

万用表

万用表测量电路的不同方面——某些点上的电压、电流和电阻(见图 4-9 )。你是得到一个模拟还是数字版本取决于你,但准备花一点钱,因为一个好的万用表是一个非常有价值的工具。它可以用来跟踪电路短路,确保您使用正确的电压,并计算出电路中两点之间存在多大的电阻。

img/323064_2_En_4_Fig9_HTML.jpg

图 4-9

模拟和数字万用表( www.digimeter.com )

当选择你的万用表时,确保它能测量交流和 DC 电压水平,因为在某些时候你可能会同时使用这两种电压。它应该能够测量电阻和连续性以及电流。当你试图追踪系统中的电线或短路时,两点连接时发出声音的仪表非常有用。然而,最重要的特征是易用性。如果你不知道如何使用万用表,你就不会使用它,你就浪费了一个重要的工具。所以,买一个你喜欢的,对你有意义的,然后花些时间学习如何使用它。

电力供应

当涉及到为您的实验和项目提供动力时,有几种不同的途径可供您选择。显然,您将经常使用电池或电池组,我将针对每个具体项目讨论这些内容。然而,当涉及到为原型供电或只是确定特定配置是否工作时,你不能错过壁式电源——目前几乎所有电子设备都配备的交流 DC 转换器。

你可以在我推荐的电子商店买到可调节的壁疣,但是你也可以去当地的旧货店。在电子产品后面的某个地方,你可能会发现一个装满废弃电源的箱子,每个大约一美元。您可以切断连接器,这样就可以直接将其插入您的试验板,或者使用如图 4-10 所示的适配器。

img/323064_2_En_4_Fig10_HTML.jpg

图 4-10

电源插头适配器

每当我看到电源时,我都会捡起来,或者当我把设备扔在家里时,我会保留它们,所以我有一个相当好的分类。尝试找到至少 9V 和 12V 的不同额定电流,因为它们是常见的电压源。例如,如果你的设备在 12V 电源下运行良好,这意味着它在你的车上也可能运行良好。如果你碰巧遇到一个不常用的电源,比如交流到交流的变压器,一定要保留它——它可能会派上用场!

面包板

在开始焊接和永久化之前,当你把电子设备组装在一起,看看一切是否正常工作时,试验板是另一个必须拥有的东西。你可以全力以赴,用一个有电源连接和仪表以及各种铃铛和哨子的豪华模型(如图 4-11 所示)。

img/323064_2_En_4_Fig11_HTML.jpg

图 4-11

原型试验板设置

或者你可以选择一个更老派的版本,如图 4-12 所示。

img/323064_2_En_4_Fig12_HTML.jpg

图 4-12

模拟试验板

无论哪种方式,只要确保可以将电阻和 IC 等器件插入其中,并且可以使用跳线连接这些器件即可。随着你技能的提高和兴趣的多样化,做好准备,有一天你的实验板会看起来像图 4-13 中的图片。

img/323064_2_En_4_Fig13_HTML.jpg

图 4-13

面包板出了大问题

是的,你要调试那团电线,不,我帮不了你。但是我同情你——真的。

工具

你需要一个电源板,但不需要任何复杂的东西。把你所有的电子产品——台灯、烙铁、Pi 等等——都插在一个电源上是个好主意,这样你就有了一个安全的方法,可以在需要的时候中止。一按开关,一切都停止了。如果可以的话,买一个内置电涌保护器的。

烙铁

你清单上的另一个关键工具应该是烙铁,这是一个你不应该吝啬成本的东西。你可以在当地百货商店买到的 9.99 美元的烙铁可能适合修补房子周围的一些电路,但如果你是一个认真的建筑者/爱好者,你需要一个高质量的可调烙铁。我有一把威勒 WES51(如图 4-14 )。

img/323064_2_En_4_Fig14_HTML.jpg

图 4-14

焊接站

这无疑是我电子职业生涯中花得最值的 100 美元。买一个带支架、可调节热度和可以互换小费的。相信我:当你可以使用正确的温度使焊料熔化而不熔化你的电路,或者当你可以使用均匀的热量来消除一个坏的焊点时,你的项目将会感谢你。

当你买熨斗的时候,也买一些焊接附件。吸钎器(一种从焊点移除熔化焊料的手持式真空泵)是必备工具,辅助工具也是必备工具(见图 4-15 )。

img/323064_2_En_4_Fig15_HTML.jpg

图 4-15

助人工具

当你需要两只手握着一个接头,另外两只手焊接那个接头,而你又烧伤了你妻子的手指很多次而你的孩子又无处可寻时,一个帮助的工具就非常方便了。你甚至可以用硬铁丝、鳄鱼夹和一个木架来制作自己的东西。

一般安全规则

在这里我可以像你妈妈一样问你是否安全。毕竟,尽管我们在本书中构建相对无害的项目,但您仍然要处理一些可能严重伤害您的组件。例如,烙铁的尖端平均温度约为 450 华氏度,热熔胶,即使是低温胶,也能在 250 华氏度左右融化。当然,编程是一项相当无害的活动,但你也会进行切割、钻孔、打磨以及其他任何可能对你造成严重伤害的工作。所以,请认真对待这次安全讲座。

加热工作

时刻记住,你周围都是会变得很热的工具和组件,你应该记住它们是什么,并相应地对待它们。不仅烙铁是热的,而且你正在使用的焊料在 350 华氏度左右熔化,这意味着你刚刚焊接的部分是热的!在触摸它之前,给它几秒钟冷却,看看你的关节是否牢固。等到你的热胶水冷却到至少凝胶状的状态,再去触摸它。从个人经验来说:热胶水最糟糕的地方是,当你把它粘在手指上时,你不能把它抖掉。相反,它会留在你的手指上,然后发出嘶嘶声

处理尖锐物体

这应该是不言而喻的,但当涉及到您的切割工具时,遵循良好的安全实践也是必要的。这意味着:

  • 从你的身体上切掉。

  • 保持你的工具锋利。

向自己砍去,即使是轻轻的或者很小的一刀,都是自找麻烦。用 X-ACTO 刀滑了一下,你可能会切腹自杀,或者至少去急诊室缝针。相信我,缝针一点都不好玩,尤其是当医生在伤口注射麻醉剂的时候。如果你完全失去一根手指,机器人实验将立即变得更加困难至少 10 %,因为你将只有 9 根手指而不是 10 根——立即减少 10%。

保持刀刃锋利,因为任何厨师都知道,钝刀是一把危险的刀。如果你的美工刀变钝了,换一个新的。同样的事情也适用于你的 X-ACTO 刀片。一把钝刀片更有可能滑倒并割伤你,而一把锋利的刀只会在你正在砍的东西上割得更深。

佩戴安全眼镜

准备一副安全眼镜或护目镜。这是没有商量余地的——如果你没有一双,在做任何实验之前先买一双。你的视力太重要了,不能因为你的钢丝钳上的一个金属飞点或者你的砂轮上的一个火花而失去它。如果你有一双不舒服的,那就换一双——如果合适,你更有可能穿上它们。我更喜欢安全眼镜,但许多人更喜欢用松紧带固定在头上的护目镜,因为它们不会脱落。无论你喜欢哪一种,都要小心保管,以避免划伤和破损,并且在工作时佩戴它们。

准备好灭火器

我给你讲个小故事吧。当我用 Pi 制作我的第一个小移动机器人时,我第一次使用锂聚合物(LiPo)电池。这是一个 11.1 伏,1300 毫安时的小电池。我把它连接到我的伺服电机上,我设法用鳄鱼夹把正负极短接在一起。

紧接在响亮的砰砰声之后!和火花,包装开始迅速升温,包装开始膨胀。我尽可能快地思考,设法断开鳄鱼夹,把包扔到地板中间,并把一杯水泼到电池上。我侥幸躲过了一次爆炸,后来我发现那些脂肪电池威力很大。

这个故事的寓意是,虽然我用了一杯水,但我身边有一个灭火器,如果需要的话,我随时可以使用,你也应该这样做。它们并不贵,而且当谈到可能从火灾中拯救你的房子或车间时,它们是非常值得的。准备一个灭火器,并确保它充满电。

另外,在你需要灭火器之前,一定要学会如何使用它。想象一下,当你在阿拉斯加的野外徒步旅行,需要避开一只熊的时候,它就像是熊喷雾。你肯定会在徒步旅行前练习使用它,因为在逃离愤怒的灰熊时阅读方向是非常困难的。你的灭火器也是类似的——当你的车间变成一个高耸的地狱的翻版时,阅读和遵循说明书会非常困难。熟悉它,希望你永远不会用到它。

手边放一个急救箱

尽管这是不言而喻的,但你要在身边准备一个急救箱。你不需要一个完整的 1 级应急装备包来进行南极探险,但是一个小的、储备充足的装备包应该在附近的某个地方。工具包里应该有一些创可贴、酒精、棉签,也许还有一些其他零碎东西。当你全身都是血时,很难焊接起来。

在通风区域工作

工作时要记住的一个重要细节是保持工作间通风良好,因为你很可能会进行打磨、油漆、锯木和其他各种可能使空气(和肺部)充满危险物质的活动。举例来说,你可能没有刷足够多的油漆来担心油漆味,但是你肯定会接触到焊料味。焊料含有铅——不多,但有一些——而且铅是有毒的。如果你接触太多太频繁,它会导致铅中毒。症状包括腹痛、意识模糊、头痛和易怒。在严重的情况下,它会导致癫痫发作甚至死亡,这两种情况都不利于进一步的机器人实验。

尽管你不太可能在焊接时接触到足以毒死自己的铅,但要知道它是有毒的。不要吸入烟雾,在接触焊接部件后,要经常彻底洗手。你应该在通风良好的地方工作,开着窗户,或者至少开着风扇。一些实验者将一个旧的电脑风扇连接到一个干燥通风软管上,以定制一个除烟解决方案。

组织你的工作场所

随着你通过购买额外的工具、零件、芯片、电路板等等来进行实验,你将需要一种方法来组织这一切。保持你的工作区域有条不紊也很容易安全,因为把所有东西都整齐地存放在原处也可以消除工作场所的危险。

至少,买几个不同尺寸的三明治袋子,这样你就可以把不同的部分分开,但是当你过了不能回头的时候,看看储存的方法。我在当地工艺品商店的串珠区运气不错,因为许多电子零件都和珠子差不多大。我的一个存储区如图 4-16 所示,而另一个存储区如图 4-17 所示。

img/323064_2_En_4_Fig17_HTML.jpg

图 4-17

项目存储区

img/323064_2_En_4_Fig16_HTML.jpg

图 4-16

小型零件储存区

注意标签;给自己买个标签制作机吧!很容易,这是我最好的一次购买——你可以给抽屉、电源、电线、孩子贴上标签。。。可能性是无限的。此外,一旦你开始着手并同时有多个项目在进行中,你会发现按项目而不是按部分来划分更容易,至少有时是这样。有些东西,比如开关,到处都在使用,但是我知道当我再次攻击我的时间喷泉或者做更多的光画的时候,我可以在那些各自的盒子里找到我一直在使用的所有部件。

我必须在这里补充一点,当谈到组织一个商店时,特别是在工具方面,可以看看亚当·沙维奇在 YouTube 上的测试系列。在许多视频中,他解释了如何整理工具,以及如何建造(如果有必要的话)存放设备的容器。它鼓舞人心,一定会给你很多灵感,即使你只是利用厨房后面的一个小角落。

总的来说,保持你的区域干净就行了。不仅在它们应该在的地方找到它们更有效率,而且在拿着 X-ACTO 刀时被电源线绊倒可能是一个危险的事件。把你的工具或零件从原处拿走,使用它,然后放回原处。(这是另一个你可能必须照我说的做,而不是照我做的地方。在整本书中,你会看到我工作空间的照片,我不能保证它永远是最整洁的地方。当我工作时,我倾向于散开。但是你明白了。)

奖励:焊接技术

在这最后一节介绍电子章节,我会给你一些提示如何焊接。焊接是一门艺术,也是一项技能,它需要练习。如果你以前从来没有焊接过,你的第一个接头肯定是凹凸不平的,丑陋的团块,但是如果你坚持下去,改善会很快。当你在一个真实的项目中完成时,仅仅花几个小时将元件焊接在一起的练习就能产生巨大的不同。

焊接基本上分为四个步骤:准备表面,必要时上锡,连接零件,加热:

  1. 准备好你的 。如果你要连接电线——连接到其他电线或另一个表面——剥去最后半英寸左右的绝缘层,将线束缠绕在一起,形成一个紧凑的线束。其他金属部件可能需要清洁,如果是特别光滑的表面,用砂纸打磨可能有助于焊料更好地附着在上面。

  2. 必要时上锡。在一个表面上镀锡就是在你把它粘在另一个表面上之前在上面熔化一点焊料。例如,在将电线连接到 IC 引脚时,这是一个很好的实践。给电线上锡时,用烙铁从底部加热,然后将焊料保持在顶部。当电线变得足够热时,焊料就会融化进去。

  3. 连接零件。如果可以的话,用机械方式连接这些部件——将电线缠绕在一起,将电线缠绕在 IC 引脚上,等等。如果这是不可能的,这就是你的帮助工具发挥作用的地方——用它来把各部分结合在一起。

  4. 热度 零件。用一个清洁的烙铁头,加热焊点,同时将焊料保持在焊点上。当连接足够热时,焊料会熔化并流到接头上。

最后一步可能是最重要的。你的熨斗尖应该是干净的;养成习惯,每当你吸完一口,开始吸另一口之前,用湿海绵擦拭。干净的尖端会更好地传热。你还应该加热接头,而不是焊料。不要将焊料熔化到烙铁的尖端,然后涂抹到连接处——你可能会产生冷焊点(如图 4-18 ),这最终肯定会失败。记住:加热零件,而不是焊料。如果你很难将焊点加热到足以让焊料熔化,你可以在接触焊点之前在烙铁顶端熔化一点焊料,因为焊料会更有效地传递热量。你应该得到一个看起来像图 4-19 中的关节。

img/323064_2_En_4_Fig19_HTML.jpg

图 4-19

良好的焊点

img/323064_2_En_4_Fig18_HTML.jpg

图 4-18

冷焊点。注意不良连接。

同样,不要过分强调你的焊接技能或缺乏焊接技能。稍加练习,你就会像专家一样焊接电路。

除了动手之外,也许学习焊接的最好方法就是观看,YouTube 的魔力让这成为可能。快速搜索“如何焊接”会出现 30 多万个结果。我不能挑出任何两三个突出的,但如果你看一些,你应该得到的想法。Makezine 是一个信息量惊人的在线博客,它有一个很好的页面,上面有学习如何焊接的资源。它位于 http://makezine.com/2006/04/10/how-to-solder-resources/

摘要

在了解了一些基本的电学原理后,你了解了实验室中一些常见的电子工具,并了解了如何安全使用它们的基本知识。我还向您介绍了焊接,并为您指出了一些可以帮助您更好地学习焊接的资源。

让我们带上工具,进入项目,从一个不需要任何工具的简单项目开始 WebBot。

五、网络机器人

任何在网上呆过一段时间的人都会告诉你,互联网上有很多信息。根据谷歌的索引,截至 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,接口选项,按右箭头键高亮显示并按回车键。确保在下一个屏幕上高亮显示(图 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 供电的猫玩具中使用它们)。利用您在这里获得的知识,您现在有能力创建一个全尺寸的安全系统,其宽度仅受您拥有的传感器数量和将它们连接在一起的电线数量的限制。

九、猫的玩具

大多数人都很熟悉“猫追小红点”的模式。它非常受欢迎,甚至在《怪物史莱克》的一部电影中有一个简短的场景。有些猫会追逐激光点,直到它们掉下来。有些人只会追逐一小会儿。无论如何,任何养猫的人都可能在某个时候和他们的猫朋友玩耍时使用了激光指示器。

但是如果你不在的时候可以逗逗你的猫不是很好吗?与你所想的相反,激光笔并不一定要由人来握持和控制。只需一点编程和机械工程魔法,你就可以拥有一个自主猫玩具。

然而,在我们将在本章构建的猫玩具项目中,我们不会就此罢休。我们就是不能。毕竟,不管你的猫在不在,让玩具一直动是没有意义的,不是吗?因此,我们将添加一些特殊的东西—红外传感器。这样,只有当你的猫在的时候它才会打开,当猫离开房间的时候它就会关闭。

你准备好了吗?让我们从获得制作猫玩具所需的零件开始。

零件购物清单

这只猫玩具的一个很好的特点是,除了简单和自制之外,零件实际上相当便宜。您将需要以下内容:

img/323064_2_En_9_Fig1_HTML.jpg

图 9-1

与猫玩具一起使用的普通激光笔

  • 覆盆子馅饼

  • 两个标准(不是连续)伺服系统——我推荐视差 900-00005 ( http://www.parallax.com/product/900-00005 ),但任何型号都可以

  • 一个便宜的激光笔,你可以在宠物店花大约 10 美元买到(见图 9-1

  • PIR 运动传感器( http://parallax.com/product/910-28027 )

  • 胶水/环氧树脂

  • 其他电线(红色、黑色等)

  • 平头螺钉

  • 电工胶带

  • 冰棒棍

  • 9V 电池

  • 一个可以装下所有东西的容器——我用了一小段 PVC 管

玩具背后的概念

让这个玩具工作的关键是随机运动。如果你给玩具编程做出一系列同心圆,一个接一个,然后重复图案,用不了多久,你的猫就能认出图案,并感到厌烦。然而,通过使用 Python 的randomrandint函数,你可以随机化模式并让你的猫(可能还有你的小孩)开心。

另一件要记住的事情是,你将在两个轴上随机化运动——x 轴和 y 轴——同时将运动保持在一定的范围内(这是随机化函数的参数和伺服系统的运动极限出现的地方)。你将使用两个伺服系统,但它们将被连接在一起,以便控制一个激光笔的运动。为了控制伺服系统,就像本书中的其他几个项目一样,我们将使用 Pi 的 GPIO 管脚和 Python 的 GPIO 库。这个项目的另一个好处是,伺服系统消耗的能量如此之少,以至于它们可以用一个简单的 9V 电池供电。你仍然需要独立于 Pi 为它们供电(总是一个好主意,不管是什么项目),但是你不需要像其他项目那样需要一个花哨的电池设置。

创建和使用随机数

你可能认为创建和使用随机数很简单。只需调用一个函数,获得一个随机整数,然后继续。虽然这可能是它在实践中的工作方式,也是我们大多数人所想到的,但随机化输出的实际过程非常有趣——这实际上是许多计算机科学家和数学家密集研究的主题。(参见侧栏“哦,随机性。”)要在 Python 中得到一个随机数,可以使用几个内置函数。第一个是random()函数。该函数返回一个浮点数(一个浮点数)。它没有参数,而是返回一个介于 0.0 和 1.0 之间的随机数。这通常非常有用,但出于我们的目的,我们需要更大的数字,最好是整数格式,这可以用randint()函数来完成。

哦,随机性

自古以来,人类就有许多产生“随机”数字的方法,从掷硬币、掷骰子到洗牌。对于大多数应用来说,这些都可以。如果需要决定谁先踢球,可以抛硬币;从 52 张牌中选择一张足够随机,当魔术师成功猜出它的身份时,它会给人留下非常深刻的印象。

然而,使用这些方法为任何真正的数学或统计工作生成随机数有两个主要问题。首先,它们都是基于物理系统——圆形硬币在空中的翻转,或多或少的方形骰子在不平坦的地面上的滚动。因为它们是物理系统,所以永远不可能是真正随机的。给定足够多的迭代,一个模式最终基于系统中的缺陷开始出现。例如,一枚硬币,由于两面都刻有图案,所以在一面有轻微的重量偏差。如果你翻转的次数足够多,这个模式会在收集的结果中显示出来。它可能需要几百万或几十亿的翻转,但它会出现。同样,骰子永远不会是完美的正方形或完美的重量,在足够数量的滚动后,最终会偏向一侧。

这些生成随机数的方法的第二个问题是,它们花费太多时间。如果你需要一批一百万个随机数,你将会掷一枚硬币很长时间来得到所有这些数字。只是对于大批量数据来说不切实际。

但是计算机在处理大量的数字和数据方面表现出色,它们可以以令人难以置信的速度生成数据。一般的台式计算机理论上可以处理 70 亿次浮点运算。(也就是每秒 70 亿次浮点运算。)在这个速度下,生成一百万个随机数需要。。。让我想想。。。拿着这两个。。。除以黄色。。。嗯。。。大约 7 毫秒。比洗牌快多了。

然而,计算机也是物理系统。是的,你是从计算机中央处理器的“网络空间”中产生数字的,但是那个处理器是一个带有物理晶体管和导线的物理硅芯片。无论你用什么程序来生成这些随机数,它们最终都会显示出一种模式,表明它们不是真正的随机。因此,你可以看到数学家和科学家对随机数的兴趣。一个真正的随机数生成器在许多科学领域都非常有用,尤其是在密码学领域。大多数密码是基于随机散列码的;一个真正随机的代码将会更加难以破解,这也是人们如此感兴趣的原因之一。

当前的随机数生成器通过使用产生长串伪随机数的算法来工作,通常基于乘法和模数运算的组合。根据算法的质量,这样生成的数字可能是也可能不是加密的,尽管它们对于视频游戏这样的应用来说通常是足够随机的。换句话说,你用来生成随机数的算法可能不会阻止超级计算机破解代码的协同努力,但当你玩使命召唤:我们都死在街区六年级学生手中的那一天时,它足以产生对手。

这种生成过程就是为什么当您开始一个将使用随机数的程序时,您必须用另一个数“播种”随机数生成器,例如今天的日期或您计算机上的系统时间。随机种子只是一个用来初始化程序中随机数向量算法的数字。只要原始种子被忽略,后续的初始化应该提供足够的随机数。是的,最终会出现一种模式——这是不可避免的——但是随机种子发生器应该足够随机以满足大多数需求,尤其是随机运动的猫玩具。薛定谔的猫可能不会被愚弄,但你的猫应该被愚弄。(抱歉,这只是一个小小的物理幽默。)

根据 Python 文档,randint(a, b)返回一个随机数n,这样n就在ab之间,包含这两个值。换句话说,下面的代码

>>> import random
>>> x = random.randint(1, 10)
>>> print x

应该返回1, 2, 3, 4, 5, 6, 7, 8, 9,或者10。我们将用它来为我们使用的伺服系统生成位置。

注意

Python 文档可以在 http://docs.python.org 找到。我强烈建议,当你学习语言的时候,养成查阅文档的习惯。您也可以在交互式 Python 提示符下键入help(function)来获得相同的阅读材料。

使用 GPIO 库

现在你已经知道我们如何生成随机数,你需要知道如何控制将连接到 Pi 的伺服系统。幸运的是,有一个专门为此目的设计的 Python 库,并且预装在 Pi 中。这个库使我们能够访问 Pi 的通用输入输出(GPIO)引脚,简称为RPi.GPIO

如果您还没有安装 Python 开发库,那么您可能需要手动安装。首先,通过键入以下命令确保您的 Pi 是最新的

sudo apt-get update

然后通过键入以下命令来安装这些包

sudo apt-get install python-dev

我们现在可以开始键入这个项目的最终代码。记住,它可以从Apress. com作为cat-toy.py获得。现在,要访问 Pi,请在程序的第一行中调用以下内容:

import RPi.GPIO as GPIO

然后,通过键入以下命令进行配置

GPIO.setmode(GPIO.BOARD)

这使您可以根据标准引脚排列图上的标签来识别引脚,如图 9-2 所示。

img/323064_2_En_9_Fig2_HTML.jpg

图 9-2

GPIO 引脚的引脚排列

注意

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

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

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)

我们将使用两个引脚进行伺服控制,一个引脚为激光笔供电,另一个引脚从红外传感器读取输入。

控制伺服系统

伺服系统是许多不同应用的重要组成部分,从无线电控制的车辆到高端机器人。从本质上讲,伺服系统只不过是一台 DC 发动机。然而,在软件的帮助下,你可以对电机的旋转进行非常精细的控制。例如,如果您需要它旋转 27.5 度,然后停止(假设伺服能够),您可以通过编程向它发送该命令。

那么,如何利用 Pi 上的 GPIO 引脚实现这一点呢?不幸的是,你不能只是将伺服的信号线(通常是白色的)插入 GPIO 输出引脚,给它一个正负电压,然后期望它工作。有可能,但也有可能没有。

答案在于如何控制伺服系统。作为硬件的模拟部分,它们早于今天的大多数数字硬件,包括圆周率。它们使用脉宽调制(PWM)信号工作。要控制它们,你必须能够通过你正在使用的任何机制发送 PWM 信号,无论是 Arduino 引脚、串行电缆还是 Raspberry Pi 的 GPIO 引脚。如果你想设置一个伺服系统的位置,你需要给它发送规则的电流脉冲——每秒 50 次是平均脉冲速度——而不是一个长脉冲。如果你想知道,每秒 50 次相当于每 20 毫秒(ms)一次脉冲。

此外,脉冲的长度决定了伺服系统的位置。例如,每 20 毫秒发送一次的 1.5 毫秒的 on 脉冲将把伺服系统发送到中心位置。较短的脉冲会使它转向一个方向,而较长的脉冲会使它转向另一个方向。因此,通过精确计时发送给伺服系统的脉冲长度,可以精确定位伺服磁头。

图 9-3 中的图表最好地说明了这一点。

img/323064_2_En_9_Fig3_HTML.jpg

图 9-3

伺服系统的占空比

如果你想把伺服发送到中间“零”位置,你每 20 毫秒发送一个 1.5 毫秒的 on 脉冲,这可以被认为是 7.5%的占空比。同样,如果你想用 0.5 毫秒的 on 脉冲逆时针转动它,它的占空比为 2.5%,较长的 2.5 毫秒 on 脉冲转换为 12.5%的占空比。换句话说,在 2.5%、7.5%或 12.5%的时间里,伺服系统被给予“高”脉冲。

这些方向适用于标准— 而非连续—伺服系统。不同之处在于,标准伺服系统使用脉冲的长度来确定它们的最终位置(以偏离中心的度数为单位),而连续伺服系统使用脉冲的长度来确定它们应该转动的速度。标准伺服将移动到其目的地位置,然后停止,直到新的命令被发送;连续伺服几乎总是在移动,移动的速度由脉冲长度决定。虽然你可以为猫玩具使用任何一种类型的伺服系统,但使用标准伺服系统更有意义,因此既有精确定位的能力,又有完全停止的能力,让猫至少暂时“抓住”小红点。

然而,这种移动伺服系统的方法的问题是,很难使用 Pi 和 Python 向 GPIO 引脚发送毫秒级脉冲。像 Pi 上运行的所有其他进程一样,Python 不断被系统级运行的进程中断,至少可以说,这使得 Python 程序精确计时脉冲是不切实际的。然而,GPIO 库又一次提供了我们需要的东西:您可以使用该库将 GPIO 引脚设置为 PWM 引脚,赋予它向该引脚发送正确长度脉冲所需的占空比。

因此,虽然理论上有可能编写这样的脚本:

while True:
    GPIO.output (11, 1)
    time.sleep (0.0015)
    GPIO.output (11, 0)
    time.sleep (0.0025)

如果成功的话,结果很可能完全出乎意料。相反,我们可以做的是使用RPi.GPIO库函数,通过输入以下命令,将伺服的信号引脚(此处示例中的引脚 11)设置为 PWM 输出引脚

p = GPIO.PWM(11, 50)

在这种情况下,50 将脉冲设置为 50Hz(每 20 毫秒一个脉冲),这是伺服工作所需的频率。然后,我们可以通过键入以下命令将引脚的占空比设置为 7.5%

p.start (7.5)

如果我们将p.start(7.5)放在一个while循环中,结果是伺服将移动到中心位置,然后停留在那里。用p.ChangeDutyCycle()改变占空比将允许我们在不同的方向上移动伺服系统,这就是我们对猫玩具的追求。因此,例如,要查看您的伺服来回移动,请尝试以下脚本:

import RPi.GPIO as GPIO
import time

GPIO.setmode (GPIO.BOARD)
GPIO.setup (11, GPIO.OUT)

p = GPIO.PWM (11, 50)
p.start (7.5)

while True:
    p.ChangeDutyCycle (7.5)
    time.sleep (1)
    p.ChangeDutyCycle (12.5)
    time.sleep (1)
    p.ChangeDutyCycle (2.5)
    time.sleep (1)

运行这个脚本应该使你的伺服来回扫描,在每次方向改变之间暂停一秒钟。

留给我们的猫玩具脚本的就是实现一些随机数。这些数字将决定哪个伺服移动,在哪个方向,以及多长时间。结果应该是一个相当随机的二维路径。

构建伺服机制

我们的猫玩具将在两个方向上扫描激光笔,这意味着我们需要一个能够做同样事情的伺服系统。虽然伺服系统通常不能进行二维运动,但我们可以使用两个相互连接的普通伺服系统轻松构建一个云台伺服机构。

注意

这个过程将永久地结合你的两个伺服系统,使它们不可分割,所以请确保你有其他人用于其他项目。但是,请记住,像您在这里制作的平移-倾斜设置对于需要在二维空间移动项目来说是一个方便的东西,您可能会再次使用它。所以,这并不是说你完全破坏了两个伺服系统。

你所需要做的就是把一个伺服机构的主体安装到另一个伺服机构的角上。为了建立安全的连接,您可能需要拆下固定底座伺服系统(为了简化任务,我们称之为 x 轴伺服系统)伺服角的螺钉,并将塑料锉平一点。您正在尝试尽可能展平伺服的顶部,使其与另一个(y 轴)伺服的主体紧密配合。

当它尽可能平整时,使用强力环氧树脂或粘合剂将 y 轴伺服机构粘合到 x 轴伺服机构的角上。我使用大猩猩胶水,得到了如图 9-4 所示的结果。

img/323064_2_En_9_Fig4_HTML.jpg

图 9-4

粘合的 x 和 y 伺服系统

激光笔现在可以(经过一些不那么小的修改)安装到顶部(y 轴)伺服。

构建激光机制

我们将使用标准的激光笔,但我们将在几个重要方面对其进行修改。最重要的方式是,我们将使用 Pi 的 GPIO 引脚供电,而不是使用电池。这很重要,因为它使我们能够通过编程来打开和关闭激光器,而不是通过摆弄按钮开关。

要改装激光笔,你需要一些绝缘胶带和一个大约两英寸长的平头螺钉,螺钉头比激光笔的内径稍小一点。用绝缘胶带包裹螺丝,使其紧贴指针内部。如果有必要,当你完成后,切断胶带的末端(如图 9-5 所示),这样螺钉的尖端就露出来了。

img/323064_2_En_9_Fig5_HTML.jpg

图 9-5

激光笔的螺旋机构

拆下激光笔的底座,取出电池。首先将螺钉头推入指针主体,使螺钉头向下推动通常由电池压住的内部弹簧。你可能不得不玩弄你用来包裹螺丝的胶带的数量;你希望它足够紧,能够压下弹簧并保持在原位,而不会有移动的危险。

我们还需要用胶带把激光笔的电源按钮粘住,这样它就会一直开着。如我所说,我们将负责从 Pi 给指针供电。用一条胶带包住指针,按住按钮。

此时,你应该有类似于图 9-6 中图像的东西。

img/323064_2_En_9_Fig6_HTML.jpg

图 9-6

完整的激光指示器机构

如果你想测试你的工作(不是一个坏主意),使用几个鳄鱼夹连接螺丝的点到引脚 6(接地引脚),然后连接指针的身体到引脚 1 (3.3V)。激光器应该亮起,表明您直接从 Pi 的电源引脚为其供电。如果什么都没发生,确保螺丝头压在弹簧上,电源按钮用胶带牢牢固定,所有连接都很牢固。一旦连接牢固,你就可以把激光安装到你的二维伺服装置上了。

将激光器连接到伺服系统

将激光器连接到伺服系统可能是这个项目中最简单的部分。如果你和我一样,你不会想把激光永久地连接到你的云台伺服系统上,因为这个系统可以在其他项目中派上用场。所以,我们需要找到一种方法来暂时把激光附加到伺服喇叭上。

我用一根冰棒棒把它拉下来。我们可以把激光粘在冰棒棍上,然后把棍拧到伺服喇叭上。这可以用伺服系统附带的螺丝来完成(你还有它们,不是吗?)或者你能在车间里找到的最小的螺丝钉。相信我——那些是伺服喇叭上非常小的孔。

使用强力胶(同样,我喜欢大猩猩胶),将激光组件粘贴到冰棒棍上。当该区域干燥时,使用小螺钉将冰棒棒连接到伺服喇叭上。当你完成后,你应该有一个看起来像图 9-7 的设备。

img/323064_2_En_9_Fig7_HTML.jpg

图 9-7

安装在伺服系统上的激光器

这里需要注意的是:花一些时间定位激光笔及其相关的伺服系统,这样无论两个伺服系统在循环中的哪个位置,激光都可以自由旋转。显然,最简单的方法是移除将伺服喇叭固定在伺服系统上的螺钉,并根据伺服系统的行程弧线重新定位喇叭。然后,运行两个伺服通过所有可能的位置,并确保您的机制没有绑定在其运动的任何一点。由于标准的伺服系统只能通过大约 180 度的圆弧,你应该能够为所有部件找到一个合适的位置。

这个项目的最后一个组成部分是连接运动传感器。

连接运动传感器

运动传感器(如图 9-8 所示)不仅能节省你的电池,还能极大地促进的酷因素:只有当你的猫(或狗,或室友,或大脚野人)靠近它时,你的玩具才会打开。

img/323064_2_En_9_Fig8_HTML.jpg

图 9-8

视差红外传感器

连接红外传感器很简单:正极和负极引脚连接到电源,第三个引脚(图 9-8 中最左边的引脚)连接到配置为输入的 Pi 的 GPIO 引脚之一。当传感器检测到运动时,它会在输出引脚上输出一个高电平信号,然后该信号会传输到 Pi 的输入引脚。通过将 GPIO 引脚配置为输入,我们可以读取该信号,并仅在该信号出现时执行控制玩具的 Python 脚本。

在我们继续之前,我需要讨论一下上拉电阻下拉电阻的重要概念,我在前一章中也谈到过。每当电子设备中有输入时,如果该输入不直接读取任何内容,则称为浮动输入。这意味着从该输入中读取的值绝对可以是任何值。我们需要定义输入的“空”状态,以便我们知道输入何时改变。

为了定义输入的“空”状态,我们通常在输入和正引脚(从而产生一个上拉电阻)或地(从而产生一个下拉电阻)之间连接一个电阻(10K 或 100K 是常见值)。使用哪一种并不重要,重要的是输入被拉高或拉低。因此,如果引脚上没有读取任何内容,并且它通过一个下拉电阻接地,则读数为“0”当它不再显示“0”时,我们就知道它正在接收输入。

对于我们的 IR 传感器,我们需要将未检测到运动时引脚上的读数定义为“低”,因此我们将使用一个下拉电阻。幸运的是,为了使这个过程简单,GPIO 库允许我们在代码中将一个引脚定义为输入时这样做,就像这样:

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

如果我们将 IR 传感器的 OUT 引脚连接到 Pi 上的引脚 11,并用前面的代码行初始化该引脚,则在检测到移动之前,引脚 11 上读取的所有内容都将为“低”。在这一点上,引脚将读取“高”,我们可以调用打开激光器并移动它的功能。

为了测试传感器和我们的编码能力,我们将相应地设置 GPIO 引脚。我们可以使用一个简单的设置来测试我们的代码;当传感器跳闸时,试验板上的 LED 会亮起。在 Python 脚本中,输入并保存以下代码:

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 上的 13 号引脚连接到一个电阻,然后连接到 LED 的正极引脚,并将 LED 的负极引脚连接到公共接地线。您应该会得到类似于图 9-9 中所示的配置。

img/323064_2_En_9_Fig9_HTML.jpg

图 9-9

红外传感器和 LED 测试设置

注意

图 9-9 中的图像是用 Fritzing ( http://www.fritzing.org )创建的,这是一个伟大的开源试验板/设计工具。它是跨平台的,非常容易使用和学习,强烈推荐。

当您运行脚本时(记住以超级用户身份执行代码,或者 sudo ,因为您正在访问 GPIO 引脚),当您在传感器周围移动您的手时,LED 应该会亮起,然后在几秒钟不动后再次熄灭。如果不起作用,检查你的连接和你的零件;相信我,烧坏的 LED 会导致各种令人头疼的故障排除问题!

如果一切按计划进行,我们现在可以给玩具布线并完成所有连接。

连接所有的位

在您成功测试了红外传感器、操作它所需的代码以及用于操作伺服系统的代码之后,当所有东西都连接到其他东西时,就该将所有东西都连接起来并进行所有连接了。这就是小型试验板派上用场的地方——你可以将所有的地面连接在一起(绝对必要的),并根据需要为所有东西供电。我使用了 9V 电池的两个伺服,但你可以随意尝试不同的电池。在这种情况下,没有必要像我们在其他一些项目中那样使用可充电的 RC 电池,因为重量不是一个真正的问题,而且伺服系统不会太快耗尽电池,因为它们不是一直在运行——这要归功于传感器。然而,你确实需要用一个独立的电源给伺服系统供电,而不是你用来给 Pi 供电的电源;否则,你可能会得到不断冻结和崩溃。对于具有两个电源通道的试验板,您可以将+9V 沿一个正极通道向下,将 Pi 上的 2 号引脚连接到另一个正极通道,然后将两个负极连接在一起。然后,您可以将激光电源和红外传感器电源连接到 Pi 的电源通道,并将两个伺服系统连接到 9V 电源通道,这样您的所有地线就会连在一起。参见图 9-10 ,图中显示了使用试验板连接在一起的各个部件。

img/323064_2_En_9_Fig10_HTML.jpg

图 9-10

最终组件连接

图 9-10 并不完全准确,因为激光笔是物理连接到伺服系统的,但你可以看到电气连接。指针由引脚#11 供电,伺服系统由引脚#13 和#15 供电,引脚#19 是传感器输入。然后,每样东西都被赋予各自的电压使其工作。

有几个机械工程任务涉及到这个玩具的建设,至少是最好的方法来永久连接电源和地线的激光笔。虽然鳄鱼夹很适合测试,但一旦伺服系统开始将指针急拉成圈,它们就要被取消了。

最好的解决办法是将电线焊接到指针部分。如果可以,用砂纸打磨螺丝和指针外壳的尖端。如果有空间,在指针外壳上钻一个小孔来固定正极导线。然后,附上你的电线和焊接一切。您的结果可能会有所不同,这取决于您的焊接能力和您正在使用的材料。你甚至可以使用胶水,只要你不要让电线和指针部件的金属触点之间沾上胶水。所有的电线都必须连接牢固,这一点很重要。

最后一步是将整个设备安装在某种容器中,以保持其位置,并保护机器的内脏免受猫科动物的研究。你可以保留所有的部分,只要它不被最终用户(你的猫)发现。我倾向于在这种情况下使用 PVC 管,因为标准的伺服系统几乎完全适合两英寸内径的 PVC 管。在这种情况下,您可以将较低的伺服安装到管道的边缘,大多数电线和内脏可以安全地存放在里面,并在侧面钻一个孔用于红外传感器。Pi 不适合,但可以安全地存放在一个单独的盒子里,通过长跳线连接到管道组件。如果您决定使用 Pi Zero(它完全能够运行这段代码),它也将适合管道内部,因此这可能是需要考虑的事情。

希望你最终得到一个看起来像图 9-11 的猫玩具。

img/323064_2_En_9_Fig11_HTML.jpg

图 9-11

成品猫玩具

不好看,但是你的猫不会在意。当然,它可以用管子末端的盖子和一层油漆来装饰。可能要记住的最重要的细节(这里没有显示)是隐藏内脏和电线,以免被窥探(和爪子)。我在这里使用的 PVC 管太窄了,但是一个具有较大横截面的管可以很容易地与 Pi 和它内部的所有其他内容相配合,从而形成一个独立的单元。然后,你可以给外面加个电源开关,就万事俱备了。

应该就是这样!执行cat_toy.py脚本现在应该可以让你的猫朋友(很可能是你的人类朋友)开心几个小时。值得注意的是,你现在有知识和能力用你的树莓派瞄准并发射激光 ??。是的,它是一个小得可怜的激光笔,但这个概念可以很容易地应用于任何激光器,无论大小或功率。任何激光。

玩得开心!

最终代码

这段代码可以在Apress.com网站上以cat-toy.py的名字获得,它设置 GPIO 输出引脚,为随机数发生器播种,然后旋转伺服系统,以随机运动的方式点亮激光器。

import RPi.GPIO as GPIO
import time
import random
random.seed()

#set pins
GPIO.setmode (GPIO.BOARD)
GPIO.setwarnings (False)
GPIO.setup (11, GPIO.OUT) #laser power
GPIO.setup (13, GPIO.OUT) #X-servo
GPIO.setup (15, GPIO.OUT) #Y-servo
GPIO.setup (19, GPIO.IN, pull_up_down=GPIO.PUD_UP) #in from IR

#setup servo pwm
p = GPIO.PWM (13, 50)
q = GPIO.PWM (15, 50)

#set both servos to center to start
p.start (7.5)
q.start (7.5)

def moveServos():
    "Turns on laser and moves X- and Y-servos randomly"
    lightLaser ()

    p.ChangeDutyCycle (random.randint (8, 12))
    time.sleep (random.random())
    q.ChangeDutyCycle (random.randint (8, 12))
    time.sleep (random.random())

    p.ChangeDutyCycle (random.randint (3, 5))
    time.sleep (random.random())
    q.ChangeDutyCycle (random.randint (3, 5))
    time.sleep (random.random())

    dimLaser ()

def lightLaser():
    GPIO.output (11, 1)

def dimLaser():
    GPIO.output (11, 0)

#main loop
while True:
    #check for input from sensor
    if GPIO.input (19):
        moveServos()
        time.sleep (0.5) #wait a half sec before polling sensor
    else:
        dimLaser()
        time.sleep (0.5)

摘要

在这一章中,你成功地构建了一个两轴伺服机构,黑了一个要被树莓派发射的激光笔,并利用你的编程技能随机指向并发射激光来娱乐你的猫。

在下一章,我们将把你的圆周率拿出房子,用无线电控制的飞机把它送上天空。

十、无线电控制的飞机

我们许多人长久以来都梦想着飞翔,梦想着在空中翱翔,像鸟儿一样自由。或者,就像飞行员小约翰·马吉所说的那样,挣脱“地球粗暴的束缚”,用“镀银的翅膀”在天空中舞蹈,轻松优雅地登上“狂风呼啸的高峰”

不幸的是,摆脱地球的束缚通常需要我们没有的时间和金钱,这可能部分解释了无线电控制(RC)飞机的出现。虽然我们可能负担不起地面或飞行学校的费用,但 1:12 比例的 Piper Cub 可以让我们感觉不那么踏实,我们有机会驾驶真正的飞机,而不必离开陆地。

然而,问题是,虽然我们可以从地面控制飞机,但这并不完全像真的在那里。虽然有复杂、昂贵的方法将小型摄像机连接到你的遥控飞机或无人机上,但如果你可以用你的 Pi 做类似的事情,那就太好了。如果你能跟踪你的飞行,然后将坐标加载到谷歌地球上,看看你的飞行是什么样子,那将会非常酷。

在这个项目中,你可以做到这一点。这个项目的计划是将 Pi、摄像机和 GPS 接收器放在一架遥控飞机上,然后驾驶它。Pi 的摄像头将在飞行过程中拍照,GPS 将记录位置数据。然后,当你回家时,你将使用一个 Python 脚本将位置数据解析成一个可以上传到 Google Earth 的 KML 文件。

注意

这一章包含了一些高级的编程概念——可能比你到目前为止遇到的任何东西都要高级,比如线程,甚至一点点面向对象编程(OOP)。但是它们并不复杂,我会在概念出现时解释它们。

零件购物清单

虽然这个项目不需要很多零件,但它实际上可能是本书中最昂贵的项目,因为它需要一架中型无线电控制(RC)飞机。以下是您需要的一些附加零件:

如果你碰巧已经是一个遥控爱好者,你很可能有一架飞机可以使用,但如果你是这项运动的新手,你需要购买一架好的入门飞机。作为一个业余爱好者,我可以推荐一个不错的初学者飞机 Switch,由 Flyzone(如图 10-1 )提供。

img/323064_2_En_10_Fig1_HTML.jpg

图 10-1

开关(图像飞行区飞机在 http://www.flyzoneplanes.com )

这架飞机足够坚固,足以承受你学习驾驶时的几次碰撞,足够稳定,足以让一个完全的初学者驾驶,最重要的是,足够强大,可以承载 Pi、GPS 接收器和电池的额外重量。它的名字来源于这样一个事实,当你越来越擅长飞行时,你可以将机翼从稳定的“顶部”配置移开,并将其切换到更低的“中间”配置,以进行更多的特技飞行。正如你将看到的,“顶部”配置是完美的,不仅因为它对初学者来说很容易,而且因为 Pi 和 GPS 可以舒适地位于机翼顶部。

准备好了吗?让我们首先为我们的 plane 程序创建一个目录:

mkdir plane

然后通过键入cd plane导航到其中。

现在,让 Pi 与我们的 GPS 设备通信。

将 GPS 接收器连接到 Pi

要让您的 Pi 与 GPS 接收器对话,您首先需要连接两者。为此,我们将使用名为gpsd的 Python 库和 Pi 的 UART(通用异步接收器/发送器)接口(引脚 7 和 8)。Python gpsd模块是一个更大的代码库的一部分,旨在允许 Pi 等设备监控连接的 GPS 和 AIS 接收器,并具有 C、C++、Java 和 Python 的端口。它允许您“读取”由大多数 GPS 接收器传输的国家海洋电子协会(NMEA)格式的数据。

UART 接口是一个老接口。它基本上是一个串行(RS-232)连接,但对于我们的目的来说,这就是我们所需要的。它由电源的正极(+)和负极(-)连接以及发送和接收引脚组成。首先输入下面一行,安装读取 GPS、gpsd及其相关程序所需的软件:

sudo apt-get install gpsd gpsd-clients python-gps

接下来,我们需要禁用默认的gpsd systemd服务,因为我们安装的服务应该会覆盖它。用...做这件事

sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket

现在,我们需要禁用串行 getty 服务:

sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service

我们还需要强制 Pi 的 CPU 使用固定频率,并启用 UART 接口。通常,CPU 的频率会根据负载而变化,但不幸的是,这可能会影响 GPS 模块等敏感项目。这将稍微影响您的 Pi 的性能,但您不太可能会注意到很大的差异。为此,编辑/boot/config.txt文件:

sudo nano /boot/config.txt

并将最后一行从

enable_uart=0

enable_uart=1

现在,通过键入以下命令重新启动

sudo shutdown –r now.

当您重新启动并运行时,将 GPS 接收器连接到 Pi,如下所示:

  • 将接收器的 VIN 连接到 Pi 的 5V(引脚#2)。

  • 将 GND 连接到 Pi 引脚 6。

  • 将 Rx 连接到 Pi Tx(引脚#8)。

  • 将 Tx 连接到 Pi Rx(引脚#10)。

当接收器的 LED 开始闪烁时,你就知道你有电了。我们使用的 GPS 接收器有两种闪烁速率。当它通电但没有 GPS 定位时,它每秒钟闪烁一次。当它被定位时,它每十五秒闪烁一次。

当你有了一个补丁,你可以测试你的gpsd程序。进入

sudo killall gpsd

(终止任何正在运行的实例)然后

sudo gpsd /dev/ttyS0 -f /var/run/gpsd.sock

然后,通过键入以下命令启动通用 GPS 客户端

cgps -s

客户端是一个普通的查看器;它只是获取gpsd程序正在接收的数据并显示给用户。

数据开始流动可能需要一段时间,但当它开始流动时,你应该会看到如图 10-2 所示的屏幕。

img/323064_2_En_10_Fig2_HTML.jpg

图 10-2

cgps

如果你只看到零,就意味着 GPS 找不到卫星定位。你可能要等几分钟,甚至给 GPS 一个清晰的天空视野。我的经验是,这种特殊的 GPS 板,即使没有可选天线,也非常敏感。当我添加天线时,即使在我的房子里,接收 GPS 信号也没有问题。(按“Q”停止流并返回到终端提示符。)

一旦我们知道 GPS 单元正在工作并与 Pi 通信,我们就需要将该信息转换成可以在日志文件中使用的格式。虽然我们在这里使用的通用客户端cgps对于查看坐标和测试连接很有用,但不幸的是很难从中获得有用的信息。出于这个原因,我们将使用 Python gps模块与接收者进行交互。

注意

gps模块允许你与许多不同的 GPS 接收器通信,而不仅仅是我们在这个项目中使用的那个。有一些接收器产生专有数据流,但大多数接收器输出的是与我们这里使用的芯片相同的 NMEA 格式数据。

设置日志文件

当我们从 GPS 获得流时,我们需要有一个地方来存储它以备后用,因为如果我们只是在飞行中将其打印到(非连接的)屏幕上,它不会给我们带来太多好处。我们可以做的是使用 Python 的日志模块建立一个日志文件,然后,当 Pi 返回地面时,我们可以解析该文件并将其转换成我们可以在 Google Earth 中使用的格式。

设置日志文件非常简单。从输入开始

import logging
logging.basicConfig(filename='locations.log', level=logging.DEBUG, format='%(message)s')

这两行导入模块,声明日志的文件名和记录的内容,并给出每行的格式。我们将把每个 GPS 呼叫保存为三个字符串:经度、纬度和高度 Google Earth 使用的三个坐标。(它们实际上是作为浮点数保存的,而不是字符串,这意味着当我们将它们写入日志文件时,我们必须将它们转换成字符串。)向日志文件中写入一行,格式很简单:

logging.info("logged message or string or what-have-you")

没有必要使用换行符(\n),因为每次调用logging.info()函数时,它都从新一行开始。

如果您想知道,是的,我们可以简单地将 GPS 数据写入一个常规文件,但是日志是一个重要的、有用的概念,许多程序员要么没有完全理解,要么完全跳过。对于 Python 的日志模块,您可以根据被跟踪事件的严重程度设置要输入的日志条目。可能有五种严重性:调试、信息、警告、错误和严重。

五个严重程度(级别)

虽然我使用术语严重性来描述日志条目,但也许级别可能是一个更好的术语。当一个程序执行时(不管它是用什么语言编写的),它通常会生成可以被日志模块记录的事件。调试事件是详细的,通常仅用于诊断问题。信息事件是事情正常工作的确认。警告事件就是这样做的——它们警告说,虽然事情还在运行,但在不久的将来可能会出现问题。错误和关键事件只有在某些东西中断时才会发生,而关键通常意味着程序无法继续工作。默认级别是警告,这意味着除非您以不同的方式设置您的日志记录功能,否则被赋予调试或信息严重性的事件(因为它们低于警告的)将不会被记录。

要查看运行中的日志模块,键入python启动 Python 提示符并输入以下内容:

>>> import logging
>>> logging.warning("I am a warning.")
>>> logging.info("I am an info.")

第二行将输出

WARNING:root:I am a warning

而分类为信息级别的第三行将不会被发送到控制台。另一方面,如果你进入

>>> logging.basicConfig(level=logging.DEBUG)

它将默认级别设置为 DEBUG,这意味着无论严重性如何,每个事件都将被记录或输出。输入文件名和格式,就像我们前面所做的那样,设置日志文件以及如何将事件写入其中。

注意

记录事件对于任何程序员来说都是一项重要的技能;如果你想更深入地了解 Python 的日志模块,我强烈推荐你阅读位于 http://docs.python.org/2/howto/logging.html 的 Python 文档。

格式化 KML 文件

KML 文件是一种特殊的 XML(可扩展标记语言), Google Earth 使用它来描绘地标、对象甚至路径。它看起来类似于一个 HTML 文件,有不同级别信息的开始和结束标签,如<Document></Document><coordinates></coordinates>。一旦我们有了来自 GPS 的日志文件,我们需要将包含的坐标格式化成 Google Earth 可以识别的 KML 文件。幸运的是,这非常容易,因为我们将日志文件的格式设置为只有经度、纬度和海拔高度,用空格分隔——用format='%(message)s'logging.info()行。现在,我们可以解析日志文件中的每一行,用空格和string.split()分隔,并将其写入一个预格式化的。kml 文件。通过使用write()函数,我们可以将每一行写入新文件,在脚本中称为kml,如下所示:

kml.write('<Document>blah blah blah</Document>\n')

既然我们知道最终的 KML 文件需要如何寻找谷歌地球来使用它,我们实际上可以编写一个程序,在我们的飞机离开地面之前解析该文件。这样,我们需要做的就是从实际的日志文件中获取数据输入,我们将在飞机着陆时获取这些数据。文件中不需要实际坐标的其他部分可以提前格式化。

例如,每一个兼容谷歌地球的 KML 文件都以这样一行开头

<?xml version="1.0" encoding="UTF-8" ?>

接下来是

<kml xmlns:="http://www.opengis.net/kml/2.2">
<Document>
<name>

等等。因此,我们可以编写脚本,将这些行添加到最终的plane.kml文件中。

我们将编写飞机上的代码,每 30 秒左右拍一张照片并记录当前的 GPS 位置。因为我们在特定的时间沿着特定的路线获取数据点,所以我们可以使用 KML 的path功能来创建我们的飞机到底做了什么的视觉记录。该路径将最终看起来像你在图 10-3 中看到的那样。

img/323064_2_En_10_Fig3_HTML.jpg

图 10-3

谷歌地球中的 KML 文件

请记住,因为我们每 30 秒才轮询一次 GPS 单元,所以我们不会有一条漂亮的曲线。相反,该路径将连接飞机在这些间隔的位置,并且连接将是直线。正如你在图 10-3 中看到的,我在试飞时使用了一个停车场。我给新手的建议是,如果可以的话,使用草地,因为在草地上迫降对你的飞机来说更容易!在我试飞的时候,阿拉斯加的一切都被雪覆盖着,所以我在哪里测试并不重要。

使用线程和对象

我们将在这个程序中使用的一个重要编程特性是线程。你可能以前见过他们;我甚至在本书的一两个其他项目中使用它们。线程很重要,因为它们允许你的程序和处理器同时执行几个任务,而且它们不会占用所有的内存和处理能力来完成一个简单的任务。一个简单的调用import threading给你线程的全部能力和它们能为你做的一切。

线程实际上是做什么的?

线程允许你的计算机(看起来)同时执行几个任务。我说“表面上”是因为处理器仍然一次只能执行一个进程,但线程允许它在进程之间来回切换,速度之快就好像同时执行它们一样。举个例子,假设你正在电脑上工作,一个窗口打开了文字处理器,另一个窗口打开了互联网浏览器。当文字处理器在一个线程中运行时,另一个线程(在你击键之间执行)保持你的浏览器更新,还有一个线程检查你的电子邮件客户端的新消息,等等。

在本节目中,我们将对 GPS 接收器进行轮询。通过使用线程,当我们继续获取数据时,我们的主缓冲区不会被数据填满,但是我们仍然可以将数据记录在日志文件中以备后用。为了尽可能高效地使用线程,我们将创建一个名为轮询器的对象,它将使用gps模块不时地(比如说每三秒钟)从 GPS 接收器请求信息。每次我们得到一个位置读数,我们都会更新日志并拍照。

对象、类和函数,天啊!

现在,你可能开始有点害怕了:“物品?班级?他在说什么?”引用道格拉斯·亚当斯的话,“不要惊慌。”把这看作是对面向对象编程(OOP)的一个简单、无压力的介绍。

把一个想象成一组共享某些特征的相似对象。例如,正方形、三角形和五边形都是shape类的成员——它们有边、可计算的周长和可计算的面积。一个对象是该类的一个特定成员,或实例:例如,myRectangleshape类的某个实例。

当你定义一个类时,你定义了它的特征,比如一个形状有边并且是一个封闭的对象。您还可以定义该类特有的函数。举例来说,shape类的每个成员都可以定义一个函数来指定如何计算其周长。该计算可能因单个 shape 对象而异,因此它对于该对象是唯一的,但每个 shape 对象都有一个defineArea()函数。

我们在最终程序中创建的线程将包含一个对象——Thread类的一个成员——以及一组独特的变量和函数。因此,当我们启动线程时,它将有一个相关的 GPS 轮询功能,该功能将处理位置检索和图片拍摄。

我们的线程对象将这样定义:

class myObject(threading.Thread):
    def __init__(self):
        #function used to initiate the class and thread
        threading.Thread.__init__(self)        #necessary to start the thread
    def run(self):
        #function performed while thread is running

从程序的主要部分,我们可以通过声明一个新的myObject对象(一个新线程)来启动线程:

newObject = myObject()

然后开始的时候

newObject.start()

线程现在用自己的myObject实例运行,称为newObject。我们的线程(如本章最后的代码所示)将从threading.Thread.__init__(self)开始。一旦它被启动,它将继续执行它的功能(在我们的例子中,收集 GPS 数据和拍照),直到我们退出程序。

设置自动启动

因为在将 Pi 连接到飞机上之前,当我们给 Pi 加电时,很可能没有插入显示器或键盘,所以我们需要确保我们的 GPS 日志脚本自动启动。最简单的方法是向/etc/rc.local文件添加一个条目(如侧栏“什么是 rc.local 文件?”中所解释的)).在我们的例子中,如果我们的 GPS 日志代码叫做getGPS.py,并且它存储在我们的Documents/plane文件夹中,我们可以添加这一行

/home/pi/Documents/plane/getGPS.py

rc.local文件。打开它

sudo nano /etc/rc.local

并添加一行

python /home/pi/Documents/plane/getGPS.py

到文件中最后一个exit 0行之前的文件。

rc.local 文件是什么?

rc.local文件是 Linux 内核的标准部分。它是系统启动文件rc之一,驻留在/etc/目录中。内核在启动时初始化所有设备后,会逐个检查rc文件,运行每个文件中包含的脚本。rc.local文件是最后一个运行的文件,它包含不适合任何其他文件的脚本。因此,该文件可由系统管理员编辑,并经常用于(就像这里一样)保存需要在计算机启动时运行的脚本。

在这个文件中添加脚本需要记住的一个重要细节是,因为它不是以任何特定用户的身份执行的,所以您必须给出脚本的完整的路径,例如,不仅仅是~/Documents/myscript.py,而是/home/pi/Documents/myscript.py

然而,这并不是我们需要做的全部。在 GPS 程序开始工作之前,我们需要再次打开 GPS feed,就像我们在测试通用 GPS 客户端时所做的那样(在前面的“将 GPS 接收器连接到 Pi”)。因此,我们还需要将那行代码放入/etc/rc.local:

sudo gpsd /dev/ttyS0 -F /var/run/gpsd.sock

最后,在我们开始记录之前,我们需要等待 GPS 单元对一些卫星进行定位;否则,我们将记录大量的0.0,0.0,nan坐标。(nan代表而不是数字。)我的经验是,该单元需要大约 30 秒来获得修复并开始返回真实数据,因此在启动脚本之前等待 45 秒可能是安全的。为此,只需将

sleep 45

在您刚刚添加的sudo gpsd行之后,系统将等待 45 秒,然后启动下一行中的 Python 脚本。完成后,您的/etc/rc.local文件应该像这样结束:

sudo gpsd /dev/ttyS0 –F /var/run/gpsd.sock
sleep 45
python /home/pi/Documents/plane/gpstest.py

保存并退出,脚本将在启动时运行。

连接比特

一旦你有了飞机,建设这个项目就相对容易了。你需要一个电池和一个调节器来确保你不会给它太多的能量。我特别喜欢 RC 爱好者使用的 Li-Po 电池(如图 10-4 所示),因为它们很轻,并且在一个小包装中包含大量的电力。我用的那种给我 1.3A 一小时——比我需要的时间长得多。

img/323064_2_En_10_Fig4_HTML.jpg

图 10-4

锂聚合物(脂)电池

对于电压调节器,你可以从 Adafruit 或 Sparkfun 等经销商处购买 5V 调节器,也可以像我一样,黑一个 USB 车载充电器,如图 10-5 所示。

img/323064_2_En_10_Fig5_HTML.jpg

图 10-5

被黑的车载充电器

中间的端子连接到电池的正极,一个外部的端子连接到 GND。然后,一根简单的 USB 线插入 Pi,你就有电了。

当谈到把所有东西都放在飞机上时,这有点像大杂烩。要记住的重要细节是保持飞机平衡,不要扰乱机翼上的气流。我把 GPS 放在机头上,把 Pi 粘在机翼上。在图 10-6 的图片中有点看不出来,但是相机被贴在左舷机翼上,指向地面。就在机翼后面,您可以看到将插入 Pi 的 USB 插头。整个设置看起来笨拙,但它飞得很好。

img/323064_2_En_10_Fig6_HTML.jpg

图 10-6

设置概述

图 10-7 显示了我如何将 GPS 装置连接到飞机的机头。

img/323064_2_En_10_Fig7_HTML.jpg

图 10-7

飞机机头 GPS 单元特写

图 10-8 显示了我如何将 Pi 连接到机翼上。

img/323064_2_En_10_Fig8_HTML.jpg

图 10-8

飞机机翼上的 Pi 特写

当你准备好出发并完成所有飞行前检查时,插入 Pi 并等待 45 秒钟,等待plane.py脚本启动,等待 GPS 单元获取一些卫星。然后,起飞,拍一些很棒的照片!

当您带着 Pi 回到家中时,登录到它并运行。kml 转换脚本,这里叫做kml.py。该脚本将打开由plane.py脚本创建的locations.log文件,解析其文本,并将所有包含的位置写入一个有效的。kml 文件,名为plane.kml

然后,您可以将该文件传输到任何安装了 Google Earth 的计算机上。当它被加载到你的计算机上时,右击该文件并打开“打开方式”菜单。在程序选项中找到“谷歌地球”,点击“打开”(如图 10-9 )。

img/323064_2_En_10_Fig9_HTML.jpg

图 10-9

在 Mac 上使用 Google Earth Pro 打开plane.kml

当文件被加载时,你会得到一个类似本章前面图 10-3 中停车场的图像。与此同时,相机拍摄的照片将与您的gpstest.py文件放在同一个文件夹中,或者您在脚本中指定的任何位置。(有关示例,请参见本章末尾的最终代码。)

这里有一个最后的提示:因为您将gps脚本放在了您的/etc/rc.local文件中,它将在您每次启动时继续启动,直到您从文件中删除那一行。如果您想终止gps脚本,使它不在后台运行,也不占用处理器资源,但是您还没有从rc.local中删除它的代码行,请键入

top

变成一个终端。这个命令显示了 Pi 上当前运行的所有进程。要停止 python 脚本,请查找名为“Python”的进程,并注意第一列中的 PID(进程 ID)。按“Q”退出top,然后输入

sudo kill xxxx

其中 xxxx 是您之前提到的 PID。这将杀死 Python 脚本,直到您从rc.local中删除它的行并重新启动。

最终代码

最终代码由两部分组成:平面程序和 KML 转换程序。

飞机程序

这部分程序是当飞机在空中时运行的,拍摄照片并记录 GPS 坐标。从Apress.com开始,可作为plane.py使用:

import os
from gps import *
from time import *
import time
import threading
import logging
from picamera import PiCamera

#set up logfile
logging.basicConfig(filename='locations.log', level=logging.DEBUG,
format='%(message)s')

camera = PiCamera()

picnum = 0
gpsd = None

class GpsPoller(threading.Thread):
    def __init__(self):      #initializes thread
        threading.Thread.__init__(self)
        global gpsd
        gpsd = gps(mode=WATCH_ENABLE)
        self.current_value = None
        self.running = True

    def run(self):           #actions taken by thread
        global gpsd
        while gpsp.running:
            gpsd.next()

if  __name__ == '__main__':   #if in the main program section,
    gpsp = GpsPoller()       #start a thread and start logging
    try:                     #and taking pictures
        gpsp.start()
        while True:
            #log location from GPS
            logging.info(str(gpsd.fix.longitude) + " " + str(gpsd.fix.latitude) + " " +  str(gpsd.fix.altitude))

            #save numbered image in correct directory
            camera.capture("/home/pi/Documents/plane/image" + str(picnum) + ".jpg")
            picnum = picnum + 1  #increment picture number
            time.sleep(3)
    except (KeyboardInterrupt, SystemExit):
        gpsp.running = False
        gpsp.join()

KML 转换计划

这个程序在 Pi 返回地面时运行。它获取 GPS 日志文件并将其转换为 KML 文件。从Apress.com开始,可作为kml.py使用:

import string

#open files for reading and writing
gps = open('locations.log', 'r')
kml = open('plane.kml', 'w')

kml.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
kml.write('<kml xmlns:="http://www.opengis.net/kml/2.2">\n')
kml.write('<Document>\n')
kml.write('<name>Plane Path</name>\n')
kml.write('<description>Path taken by plane</description>\n')
kml.write('<Style id="yellowLineGreenPoly">\n')
kml.write('<LineStyle<color>7f00ffff</color><width>4</width></LineStyle>\n')
kml.write('<PolyStyle><color>7f00ff00</color></PolyStyle>\n')
kml.write('</Style>\n')
kml.write('Placemark><name>Plane Path</name>\n')
kml.write('<styleUrl>#yellowLineGreenPoly</styleUrl>\n')
kml.write('<LineString>\n')
kml.write('<extrude>1</extrude><tesselate>1</tesselate>\n')
kml.write('<altitudeMode>relative</altitudeMode>\n')
kml.write('<coordinates>\n')

for line in gps:
    #separate string by spaces
    coordinate = string.split(line)
    longitude = coordinate[0]
    latitude = coordinate[1]
    altitude = coordinate[2]
    kml.write(longitude + "," + latitude + "," + altitude + "\n")

kml.write('<\coordinates>\n')
kml.write('</LineString>\n')
kml.write('</Placemark>\n')
kml.write('</Document>\n')
kml.write('</kml>\n')

摘要

在本章中,我们将 GPS 连接到 Pi,并通过 Pi 的 UART 连接读取其输入。然后,我们将这些信息放入 Python 日志文件中。我们把 Pi 和 GPS 绑在一架无线电控制的飞机上,记录我们的飞行,在飞行中每隔几秒钟拍一次照片。然后,飞机着陆后,我们将 GPS 日志文件转换成 KML 文件,并将该文件放入谷歌地球,以查看我们最终飞行路径的卫星显示。这一章展示了 Raspberry Pi 真正的可移植性。

在下一章中,我们将把圆周率放在气象气球里送到高层大气中,从而得到更高的圆周率。

十一、气象气球

你可能对气象气球很熟悉。有时直径可达 20 英尺,它们可以充满氦,给定一个小的科学有效载荷,并上升到大气层的上限,在它们前进的过程中记录各种传感器读数。然后,当外部压力明显小于气球内部压力时,它们就会破裂,有效载荷在一个小降落伞的帮助下落回地球。发射气球的小组追踪下落的包裹并取回数据。通过这种方式,科学家和业余爱好者可以对大气层的上层了解很多。

虽然在前一章中用树莓派操作无线电控制的飞机很酷,但它也有些乏味,因为我们记录了它的位置并将它的路径数据上传到 Google Earth。这个项目背后的概念是建造一个气象气球,非常简单,但更先进。我们将充气并发射一个小型气象气球,能够将树莓派送到至少 30,000 英尺的高空。然后,我们将对 Pi 进行编程,使其经常拍照,为我们的飞行提供图像记录,我们还将使用一个小型 GPS 设备来记录 Pi 的行程。

但是我们不会就此止步,因为那有点无聊,而且已经被许多不同的爱好者和专业人士做过了。此外,我们将通过编程 Pi 来记录自己每隔 15 秒左右说出其坐标,从而实时更新气球正在做什么——它的纬度、经度,甚至高度。然后,我们将通过地面无线电广播这段录音。我们所需要做的就是把我们的小型调频收音机调到预先指定的频率,这样我们就能听到我们的气球在和我们说话,给我们实时更新它的状况。

我们去买零件吧!

警告

在美国,FAA 法规要求您在发射前 6 至 24 小时通知该机构相关信息,如发射时间和地点、预测高度、气球描述和着陆位置预测。联邦航空局还要求你跟踪气球的位置,并有能力更新联邦航空局的信息,如果需要的话。系留气球和自由气球的规则不同;更多信息,请参见 http://www.gpo.gov/fdsys/pkg/CFR-2012-title14-vol2/pdf/CFR-2012-title14-vol2-part101.pdf 的完整规定。

零件购物清单

像遥控飞机一样,这是书中最昂贵的项目之一,因为气象气球可能有点贵,你需要从聚会用品商店或焊接用品商店购买或租赁一罐氦气。这是你需要的:

  • 带摄像板的树莓皮

  • LiPo 电池和 5V 调节器为 Pi 供电

  • 直径 6-7 英尺的乳胶气象气球

  • GPS 接收器( https://www.adafruit.com/products/746 )

  • GPS 天线(可选)( https://www.adafruit.com/products/851 )

  • 手持式调幅/调频收音机

  • 10 英尺长的电线

  • 小型泡沫聚苯乙烯冷却器

  • 模型火箭降落伞

  • 暖手宝

  • 钓鱼线——至少 5000 码

  • 1 英尺长的手术导管,内径约为 1 英寸

  • 氦——大约 250 立方英尺

  • 管道胶带、电工胶带、橡皮筋、拉链

设置 GPS 接收器

就像第十章中的遥控飞机项目一样,这个项目的一个重要组成部分就是让 GPS 设备和你的 Pi 一起运行。为此,您将安装 Python gpsd模块,并使用 Pi 的通用异步接收器/发送器(UART)引脚#7 和#8。Python gpsd模块是一个更大的代码库的一部分,旨在允许 Pi 等设备监控连接的 GPS 和自动识别系统(AIS)接收器,并具有 C、C++、Java 和 Python 的端口。它允许您“读取”由大多数 GPS 接收器传输的国家海洋电子协会格式的数据。

UART 接口是旧的;它基本上是一个串行(RS-232)连接,但对于我们的目的来说,这就是我们所需要的。它由电源的正极(+)和负极(–)连接以及发送和接收引脚组成。首先输入下面一行,安装读取 GPS、gpsd及其相关程序所需的软件:

sudo apt-get install gpsd gpsd-clients python-gps

接下来,我们需要禁用默认的gpsd systemd服务,因为我们安装的服务应该会覆盖它。用...做这件事

sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket

现在,我们需要禁用串行 getty 服务:

sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl disable serial-getty@ttyS0.service

我们还需要强制 Pi 的 CPU 使用固定频率,并启用 UART 接口。通常,CPU 的频率会根据负载而变化,但不幸的是,这可能会影响 GPS 模块等敏感项目。这将稍微影响您的 Pi 的性能,但您不太可能会注意到很大的差异。为此,编辑/boot/config.txt文件:

sudo nano /boot/config.txt

并将最后一行从

enable_uart=0

enable_uart=1

现在,通过键入以下命令重新启动

sudo shutdown –r now.

当您重新启动并运行时,将 GPS 接收器连接到 Pi,如下所示:

  • 将接收器的 VIN 连接到 Pi 的 5V(引脚#2)。

  • 将 GND 连接到 Pi 引脚 6。

  • 将 Rx 连接到 Pi Tx(引脚#8)。

  • 将 Tx 连接到 Pi Rx(引脚#10)。

当接收器的 LED 开始闪烁时,你就知道你有电了。我们使用的 GPS 接收器有两种闪烁速率。当它通电但没有 GPS 定位时,它每秒钟闪烁一次。当它被定位时,它每十五秒闪烁一次。

当你有了一个补丁,你可以测试你的gpsd程序。进入

sudo killall gpsd

(终止任何正在运行的实例)然后

sudo gpsd /dev/ttyS0 -f /var/run/gpsd.sock

然后,通过键入以下命令启动通用 GPS 客户端

cgps -s

客户端是一个普通的查看器;它只是获取gpsd程序正在接收的数据并显示给用户。

数据开始流动可能需要一段时间,但当它开始流动时,你应该会看到如图 11-1 所示的屏幕。

img/323064_2_En_11_Fig1_HTML.jpg

图 11-1

cgps

我们不会使用cgps显示屏——这只是一种确保 GPS 单元正确连接并正常工作的简便方法。我们将使用 Python 的gps模块与 GPS 板通信。

存储 GPS 数据

在这个项目中,您将把 GPS 数据写入一个文件,以便以后读取、记录和传输。我们可以为此使用日志文件,就像我们对 RC plane 项目所做的那样,但是学习使用 Python 读写普通文件也很重要。在您的终端中,启动 Python 提示符并键入

f = open('testfile.txt', 'w')

这将打开一个文件—在本例中是testfile.txt。第二个参数可以是以下四个值之一:

  • 文件是只读的。

  • ‘w’文件只用于写入。(每次打开文件时,以前的数据将被清除。)

  • 'a' 文件将被追加到。

  • 该文件以可读写方式打开。

要继续,请键入

f.write('This is a test of my file-writing')
f.close()

如果您现在通过按下Ctrl+d退出 Python 提示符并列出您的目录内容,您将看到testfile.txt被列出,查看它的内容将显示您刚刚输入的行。现在,再试一次:启动另一个 Python 提示符并键入

f = open('testfile.txt', 'w')
f.write('This text should overwrite the first text')
f.close()

并退出 Python 提示符。因为您使用'w'参数打开了文件,所以所有原始文本都被覆盖。这就是你要在我们的 GPS 定位文件中做的事情。我们对像以前一样在飞机日志文件中保存位置不感兴趣;相反,每个位置将被记录,然后传输,然后我们可以通过用'w'标志打开它,用下一个位置覆盖它。

安装 PiFM

要让你的 Pi 通过无线电和你说话,你需要使用一个由伦敦帝国学院机器人协会成员开发的方便的小工具。该模块名为PiFM,使用 Pi 的现有硬件将其变成一个漂亮的小型调频发射机。

要使用该模块,您首先需要下载它。在您的/balloon目录中,打开一个终端并键入

wget http://omattos.com/pifm.tar.gz

下载完成后,通过键入

tar -xvzf pifm.tar.gz

这将把编译后的二进制文件、源代码和一些声音文件放在您的目录中。您现在已经准备好使用PiFM包了。要进行测试,请将一根一英尺长的电线连接到您的 Pi 的 7 号引脚(GPIO 号引脚)。现在,在您的终端中,键入

sudo ./pifm sound.wav 100.0

把你的收音机调到 100.0 调频。你应该被奖励一首熟悉的曲子。如果您偶然听不到任何声音,请尝试在命令末尾添加‘22050’,因为额外的参数(声音文件的采样率)可能需要明确说明,这取决于您拥有的软件版本。

恭喜你!你把你的 Pi 变成了无线电发射机。现在,让我们把它变成一个 DJ。

安装节

我敢肯定,如果我们的电脑能和我们说话,我们大多数人都会喜欢的。幸运的是,由于我们使用的是 Linux,当谈到一个好的语音合成器程序时,我们有几个选择。我们将要使用的是 Festival,它是免费的,非常容易使用。它还附带了一个我们将使用的方便的文本到语音的录音功能。

Festival 可以从您的标准 Pi 存储库中获得,这意味着要使用它,您只需要输入

sudo apt-get install festival

它会下载并安装。一旦下载和安装过程完成,尝试一下。将一对耳机插入 Pi 的 3.5 毫米音频输出插孔。然后,在您的终端中,键入以下内容:

echo "I'm sorry Dave, I'm afraid I can't do that." | festival --tts

你的私人侦探会和你说话。(如果你不知道的话,这句话是经典电影 2001:太空 奥德赛中的计算机哈尔说的。如果你没看过,那就把这本书放下去看吧。现在。我会等的。)

好了,现在你可以让你的 Pi 说出你在命令行中告诉它的任何一行。虽然这很酷,但我们需要让它从文本文件中读取一行:类似于“当前高度 10,000 英尺,当前位置纬度 92 度,经度 164 度。”最简单的方法是使用 Festival 非常方便的text2wave功能。这个函数读取一个文件,例如position.txt,并记录下来。它的语法是

text2wave position.txt -o position.wav

现在,知道我们将每 15 秒更新一次position.txt文件,我们可以在使用PiFM广播该记录之前使用text2wave功能重新记录其内容。

然而,有一个小问题:text2wave使用 44100 kHz 的采样率对其录音进行编码,而 PiFM 需要 22050 kHz 的采样率。这需要我们工具包的另一个程序—FFMPEG。

安装 FFMPEG

在音频和视频编码器、解码器和代码转换器的世界中,FFMPEG 无疑是最流行和最强大的程序之一。它可以将电影文件从 MPG 格式转换为 AVI 格式,将 AVI 文件分离成单独的帧,甚至可以将音频从电影中分离出来,通过连续的过滤器,然后重新附加到视频中。

然而,尽管所有这些壮举都令人印象深刻,但我们需要使用它来将我们的音频从 44100 kHz 更改为 22050 kHz。首先用以下命令获取源文件

wget https://ffmpeg.org/releases/ffmpeg-snapshot-git.tar.bz2

下载完成后,用

tar -vxjf ffmpeg-snapshot-git.tar.bz2

完成提取后,用cd进入结果目录

cd ffmpeg

然后键入

./configure

配置安装。当完成后,输入

make

然后

sudo make install

安装ffmpeg库。这不是一个小图书馆,所以在它编译的时候,你可以随意喝杯咖啡或者小睡一会儿。

替代 avconv

如果从源代码安装ffmpeg让你紧张,还有另一个选择:在 Pi 上使用ffmpeg的替代品,avconv。这个库应该已经存在于您的 Pi 上,所以不需要安装。

现在,为了转换我们的position.wav文件,我们使用以下语法:

ffmpeg -i "position.wav" -y -ar 22050 "position.wav"

或者

avconv -i "position.wav" -y -ar 22050 "position.wav"

就是这样— position.wav现在已经以 22050 kHz 的采样率重新编码,并准备广播。如果我们想广播position.wav的内容,我们可以输入一个终端

sudo ./pifm position.wav 103.5 22050

(103.5 FM 是它将被广播到的频率。当然,要根据你当地的电台进行调整。)

准备 Pi

如果你在 RC 飞机章节之后阅读这一章,你可能认识这里的大部分设置,因为这些项目的许多部分是非常相似的。您需要做的第一件事是确保每次启动 Pi 时gpsd模块都会运行。为此,通过键入以下命令打开您的rc.local文件

sudo nano /etc/rc.local

并将下面一行添加到末尾:

sudo gpsd /dev/ttyS0 -F /var/run/gpsd.sock

注意

有关rc.local文件的更多信息,请参见第十章中的侧栏。

现在,每次我们启动 Pi 时,gpsd模块都会运行。

然而,我们还没有完成rc.local文件。很可能当您发射气球时,您只想给 Pi 加电并发射,而不必担心登录和启动您编写的程序。幸运的是,您也可以用rc.local文件做到这一点。在启动时,在gpsd模块发射后,在它开始记录数据之前,你会想要给你的 GPS 板几秒钟来定位一些卫星。为此,在刚刚显示的gpsd行之后,添加该行

sleep(45)

让 Pi 暂停 45 秒,然后添加一行

sudo python /home/pi/Documents/balloon/balloon.py

(当然,要确保这条线与你存放气球程序的地方相匹配。)您的气球程序现在将自动启动,在您的 GPS 模块开始从其卫星定位中读取数据 45 秒后。

使用线程和对象

我们将在这个项目中使用的一个重要编程特性是线程。如果你已经在第十章的中建造了遥控飞机项目,这将是你的旧帽子。然而,如果你没有,这里有一个快速的纲要。

线程很重要,因为它们允许你的程序和处理器同时执行几个任务,而且它们不会占用所有的内存和处理能力来完成一个简单的任务。一个简单的导入线程的调用就可以让您充分利用线程的全部功能以及它们能为您做的一切。

线程实际上是做什么的?线程允许你的计算机(看起来)同时执行几个任务。我说“表面上”是因为处理器仍然一次只能执行一个进程,但线程允许它在进程之间来回切换,速度之快就好像同时执行它们一样。有关线程的更多介绍,请参见第十章的侧栏。

我们将用它们来查询 GPS 接收器。通过使用线程,当我们继续获取数据时,我们的主缓冲区不会被数据填满,我们仍然可以将数据记录在我们的position.txt文件中以备后用。为了尽可能高效地使用线程,我们将创建一个名为轮询器的对象,它将使用gps模块每隔 15 秒向 GPS 接收器请求信息。每当我们得到一个位置读数,我们将更新文本文件并拍照。

注意

关于对象、类和面向对象编程的复习,请参见第十章的侧栏。

我们的线程对象将这样定义:

class myObject(threading.Thread):
       def __init__(self):
       #function used to initiate the class and thread
       threading.Thread.__init__(self)        #necessary to start the thread
       def run(self):
       #function performed while thread is running

从程序的主要部分,我们可以通过声明一个新的myObject对象(一个新线程)来启动线程:

newObject = myObject()

然后开始

newObject.start()

线程现在用自己的myObject实例运行,称为newObject。我们的线程(如本章最后的代码所示)将从

threading.Thread.__init__(self)

一旦它被启动,它将继续执行它的功能(在我们的例子中,收集 GPS 数据,传输它,并拍照),直到我们退出程序或关闭 Pi。

连接比特

这个气象气球项目的建设可能相当复杂,所以留出几个小时来做好一切准备。

首先要做的是安装氦气罐。当你租油箱时,它应该配有一个调节器和一个用来给气球充气的奶嘴。这可以用来填充你的气球,只需一个小小的改动。将外科手术导管套在调节器上,并用胶带牢牢固定。然后可以将管子插入气球的颈部进行填充(如图 11-2 所示),并用拉链固定。

img/323064_2_En_11_Fig2_HTML.jpg

图 11-2

调节器至气球颈部设置

调节器与气球的连接安全后,你就可以把你的有效载荷组装起来了。在你的聚苯乙烯泡沫冷却器的底部切一个足够大的小洞,以适合 Pi 的相机板。将相机放入孔中,必要时用胶带固定。(见图 11-3 。)在冷却器的底部,安装 Pi 及其电池组,准备在任务启动时连接。

img/323064_2_En_11_Fig3_HTML.jpg

图 11-3

放置在冷却器中的摄像机

在冷却器底部戳一个小洞。这是 GPS 天线、FM 天线和气球系绳的位置。将它们穿过孔,并将 FM 天线连接到 Pi 的 7 号引脚。如果尚未将 GPS 板连接到 Pi(参见本章前面的“设置 GPS 接收器”一节),则将天线连接到 GPS。两个天线应该自由悬挂在底部,系绳应该连接到您的渔线线轴上。将系绳的自由端固定在你的冷却器上,不要撕裂冷却器;我切了一段聚氯乙烯管的长度冷却器和系绳。(见图 11-4 。)

img/323064_2_En_11_Fig4_HTML.jpg

图 11-4

冷却器内部显示了连接到 PVC 管的系绳

现在,打开暖手宝,通过摇晃和混合里面的东西来激活它们。把它们放在冷却器的底部,把你的 Pi 和电池组放在上面。暖手宝很重要,因为高层大气会变得相当冷——冷到足以让你的电子设备停止工作。加温器应该使冷却器内部保持足够的温度,以使您的 Pi 在飞行的顶点继续工作。

你需要把降落伞系在冷藏箱的盖子上,以防气球爆炸,从而保护你的 Pi。我发现最好的方法是在盖子上戳一个洞,将降落伞绳穿过这个洞,然后用热胶水将它们固定在盖子上。然后,在降落伞上松松地系一根绳子——系得足够紧,以便在上升时保持降落伞关闭,但又足够松,以便在冷却器开始自由下落时让降落伞挣脱并打开。

*最后要做的事情是给 Pi 加电,把盖子紧紧地贴在冷却器上,然后把它贴在装满的气球上。当气球达到所需的体积时(这将随着你的有效载荷的重量而变化,所以有必要做一些实验),将它从油箱中取出,并用拉链或橡皮筋系住末端。然后,用拉链将气球的颈部系在你的冷却器上,然后松开。气球将飘入大气层,一边飘一边拍照。与此同时,将你的收音机调到 103.5 FM(或者你在最终代码中设置的任何一个电台),当你的气球准确地告诉你它有多高时,你可以收听实时更新。除非它爆裂(总是一种明显的可能性),否则气球应该行进到所附钓鱼线的末端,这是一个尽可能获得更多线的好理由,以便允许你的气球尽可能高地上升。当要取回你的气球时,用附带的钓鱼线把它卷回来。为了保护你的手臂肌肉,你可能想把钓鱼线线轴连接到电钻上。

注意

在放飞气球之前,请咨询当地的 FAA 分支机构或同等机构,以确定最佳的发射时间和地点。

查看照片结果

根据您的发射结果,您可以希望从您的 Pi 获得一些良好的高空拍摄,如图 11-5 、 11-6 、 11-7 、 11-8 和 11-9 所示。您的结果可能会有所不同,但如果您的第一次启动不顺利,您随时可以再试一次!

img/323064_2_En_11_Fig9_HTML.jpg

图 11-9

高空图片

img/323064_2_En_11_Fig8_HTML.jpg

图 11-8

高空图片

img/323064_2_En_11_Fig7_HTML.jpg

图 11-7

高空图片

img/323064_2_En_11_Fig6_HTML.jpg

图 11-6

高空图片

img/323064_2_En_11_Fig5_HTML.jpg

图 11-5

高空图片

最终代码

该代码(从Apress.com开始以balloon.py的形式提供)将查询 GPS 板,记录并传输其坐标,并定期用车载摄像头拍照:

import os
from gps import *
import time
import threading
import subprocess

#set up variables
picnum = 0
gpsd = None

class GpsPoller(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        global gpsd
        global picnum
        gpsd = gps(mode=WATCH_ENABLE)
        self.current_value = None
        self.running = True
    def run(self):
        global gpsd
        while gpsp.running:
            gpsd.next()

if __name__ == '__main__':
    gpsp = GpsPoller()
    try:
        gpsp.start()
        while True:
            f = open('position.txt', 'w')
            curAlt = gpsd.fix.altitude
            curLong = gpsd.fix.longitude
            curLat = gpsd.fix.latitude
            f.write( str(curAlt) + "feet altitude," + str(curLong) + "degrees longitude," + str(curLat) +
            " degrees latitude")
            f.close()
            subprocess.call(["text2wave position.txt -o position.wav"], shell = True)
            subprocess.call(['ffmpeg -i "position.wav" -y -ar 22050 "position.wav"'], shell = True)
            subprocess.call(["sudo ./pifm position.wav 103.5 22050"], shell = True)
            subprocess.call(["raspistill -o /home/pi/Documents/balloon/image" + str(picnum) + ".jpg"], shell=True)
            picnum = picnum + 1
            time.sleep(15)
    except (KeyboardInterrupt, SystemExit):
        gpsp.running = False
        gpsp.join()

摘要

在本章中,您对您的 Pi 进行了编程,以从 GPS 模块获取其位置,并将其位置传输给您,以便您可以停留在地面上,并将它发送到高层大气中进行拍照。您再次使用了线程,并且更多地使用了面向对象的代码,我希望您从高层获得了一些好的图片。

在下一章中,我们将反其道而行之,将圆周率发送到水下。*

十二、潜水艇

几十年来,潜水器,无论是遥控潜水器还是自主潜水器,都被用于科学研究和私营企业。他们研究了贫瘠的洲际平原上的生命,探索了大西洋中部的火山口,去了人类不可能去的地方。在商业领域,他们在 2010 年深水地平线石油灾难中成为焦点。一队潜水器被用来覆盖油井,并在 5000 英尺的深度阻止泄漏,这远远低于人类潜水员所能达到的深度。它们通常用于深海石油钻塔和近海波浪农场的维护。

潜水器基本上有两种:ROV 和 AUV。 ROV 代表遥控 运载工具,描述了一种通过系绳从船上远程控制的潜艇——一种为潜艇提供动力并与其船上系统进行双向通信的电缆。通常情况下,船上的摄像机通过绳索将视频信号发送到控制室的监视器上,在控制室,受过专门训练的操作员使用视频信号通过高科技版本的 Xbox 控制器控制潜艇。遥控操作者控制潜艇的推进、转向和深度,如果装备了样本收集器,他也能操作潜水器的夹子和样本收集器。

另一方面,AUV 代表自主水下 运载工具,描述了一种无需人工干预或控制的潜水器。它可能使用也可能不使用机载相机;如果是的话,摄像头的视频通常只用于数据收集目的,而不是用于导航。AUV 具有一系列机载传感器和相对复杂的机载计算机,该计算机被编程为基于来自传感器的信息执行各种任务和运动。

使用 Raspberry Pi,你实际上可以建造一个 ROV 和一个全尺寸的 AUV,但是为了我们的目的,我们将制作一个(更简单的)遥控潜水器。我们可以使用 Pi 的机载相机来拍摄我们的水下探索,我们可以用黑掉的 Wii 双截棍来控制潜艇。你将无法通过视频导航,因为通过电缆将视频信号发送到外部显示器稍微超出了本书的范围,但只要你不进入太深,你应该能够用木筏或小船跟随潜艇,并从水面指挥它。同样,为了将项目限制在单个章节的范围内,我们也要避免深度控制;相反,我们要让潜艇保持中性浮力。

以防万一你炸了你的 pi

这个建筑包括深水和电子设备——这两样东西在历史上就像水和猫一样密切相关。如果您创建的外壳不是完全防水的,那么您的 Pi 和/或其相关部分极有可能被烤焦。考虑到这一点,您应该执行以下一项或两项操作:

  • 买一个额外的 Pi 在你的潜水器里使用。它相对便宜(特别是当你使用 Pi Zero 或 Zero W 时),如果你碰巧炒了它,你仍然可以得到你原来的 Pi,包括所有的调整和增加的模块。如果你复制你的 SD 卡(见下一个要点),潜水式 Pi 将和你的普通 Pi 完全一样。

  • 定期备份 SD 卡,就像备份电脑硬盘一样。如果你边走边备份,如果卡出了问题,你也不会损失太多辛苦。

零件购物清单

对于此版本,您将需要以下内容:

  • 树莓皮

  • Raspberry Pi 相机套件—可从 Adafruit、Amazon 和 Sparkfun 获得

  • 电机驱动器—双 L298 H 桥( http://www.sparkfun.com/products/9670 )

  • 任天堂 Wii 双节棍控制器

  • 1 Wiichuck 配接卡( https://www.dfrobot.com/product-91.html

  • 头球

  • 2 DC 汽车公司

  • 模型螺旋桨

  • 2 个电池组(RC 爱好者使用的锂聚合物电池组是一个不错的选择)

  • 1 防水外壳——想想特百惠或类似的东西

  • 1 管 5200 船用防水密封胶

  • PVC 管和弯管接头

  • 各种拉链

  • 铁丝网或塑料网

  • 电线—红色和黑色,18 号

  • 以太网电缆——25-50 英尺(如果可能的话,批量购买,因为你不需要塑料端子,只需要电缆)

  • 填塞

访问 Raspberry Pi 的 GPIO 引脚

使 Raspberry Pi 如此有用的特性之一是它的 GPIO(通用输入/输出)引脚。使用预先安装在 Pi 上的特定 Python 模块,可以直接控制 GPIO 引脚,将电压发送到外部设备并读取输入,然后在程序中使用。

GPIO:有什么大不了的?

GPIO 引脚就像旧计算机上的一些端口。一种与外部世界连接的简单方式曾经是串行端口或打印机(并行)端口。两者都可以通过正确的编程库访问,直接向每个引脚发送信号。但随着技术的进步,这两个端口都消失了,取而代之的是 USB 和以太网连接。因此,从编程的角度来看,控制外部设备变得更加困难,这就是为什么许多人对 Pi 的 GPIO 引脚提供的可能性感到兴奋。

要配置您的 Pi 以编程方式访问 GPIO 引脚,您可能需要安装正确的开发工具包和工具。只要进入

sudo apt-get install python-dev

完成后,键入

sudo apt-get install python.rpi-gpio

注意

python-rpi.gpio可能已经安装,取决于您的 Raspian 版本。根据您阅读本章的日期,它也可能返回“无法定位包”的错误没什么大不了的,很可能已经安装了。

您现在可以访问 pin 了。用于访问它们的 Python 模块是RPi.GPIO模块。它通常在程序的第一行通过键入

import RPi.GPIO as GPIO

然后通过键入以下命令进行配置

GPIO.setmode(GPIO.BOARD)

这使您可以根据标准引脚排列图上的标签来识别引脚,如图 12-1 所示。

img/323064_2_En_12_Fig1_HTML.jpg

图 12-1

树莓 Pi 3 引脚排列

注意

请记住,对于GPIO.setmode(GPIO.BOARD),当您提到 11 号引脚时,您实际上是指物理引脚#11(在图 12-1 的图中转换为 GPIO17),不是 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)

如果 pin 已被配置为输入,您可以查询它以查看与其连接的按钮或开关是否已被按下。然而,这里需要注意的一点是,如果该引脚仅被声明为输入,则它被定义为“浮动”且没有定义的电压电平。在这种情况下,我们需要将引脚接地,使其在我们按下按钮之前一直为低电平(0)。这可以通过在引脚和公共地之间放置一个电阻来实现。幸运的是,RPi.GPIO模块允许我们通过键入以下命令在软件中实现这一点:

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

此时,您可以通过键入以下命令随时“轮询”pin

if GPIO.input(11):
    print "Input was HIGH"
else:
    print "Input was LOW"

将这段简单的代码插入脚本的循环中;每次循环运行时,脚本都会检查 pin 的状态。我们将在最终的潜水器程序中使用这一功能来触发相机拍照。

安装 Pi 摄像机板

我们将要建造的潜水器将配备有拍照设备,可以按预设的时间间隔拍照,也可以按下按钮拍照。显然,这意味着我们需要安装与 Pi 接口的相机模块。

当您收到相机板时,它很可能会放在一个普通的白色盒子里,盒子里面是一个装有相机板的防静电灰色袋子。通过接触良好的地面,确保您已经释放了身体上的任何静电(因为相机对静电非常敏感);然后,从包里取出相机。

摄像头连接到 HDMI 输出和以太网端口之间板上的条形连接器。通过拉起侧面来打开连接器,它会“弹出”几毫米,这正是您所需要的。定位相机带状电缆的末端,使连接器(银色侧,而不是蓝色侧)面向 HDMI 端口。将电缆滑入连接器,直到其到底,以确保连接良好。然后,在保持电缆不动的同时,向下按压连接器的两侧,直到它们卡入到位(参见图 12-2 )。

img/323064_2_En_12_Fig2_HTML.jpg

图 12-2

连接 Pi 的摄像机电缆

当您在第三章设置 Pi 时,您应该已经启用了摄像机支持;不过,如果你没有,这并不难。在终端中,键入

sudo apt-get update

sudo apt-get upgrade

以确保您的 Pi 运行所有最新的内核补丁和软件更新。当这两个都完成时,键入

sudo raspi-config

启动配置程序。导航到“摄像机”选项,然后单击“启用”然后,选择“完成”并重启 Pi。

当 Pi 重新启动时,您可以使用其内置的相机功能raspistillraspivid来试验相机。要查看完整的命令列表,只需输入

raspivid

或者

raspistill

在命令提示符下获取说明。例如,要以“卡通”格式拍摄静态图片,只需键入

raspistill -o image.jpg -ifx cartoon

图像将保存在您所在的目录中。您可以更改图像分辨率、高度、宽度、效果和延迟,甚至可以使用-tl标志(我们稍后可能会用到)设置延时情况。

控制潜艇

为了控制潜艇,我们将使用两个 DC 电机、一个电机驱动芯片(已经放置在印刷电路板上)和一个 Wii 双节棍(如图 12-3 所示)。使用特殊的适配器,您可以访问双节棍的电线,并将它们直接连接到 Raspberry Pi,而不必从控制器的末端切断连接器。

img/323064_2_En_12_Fig3_HTML.jpg

图 12-3

Wii 双截棍

双截棍通过一种被称为 I2C 的协议进行通信,或 I 2 C,或 IIC,代表内部集成电路。如果你按顺序完成了这些项目,你可能还记得《I2C 协议》第六章“气象站”I2C 是由飞利浦在 20 世纪 80 年代早期创建的,作为不同设备在单条总线(通信线)上进行通信的一种方式。此后,它经历了几次修订,但基本概念保持不变。在 I2C 总线上,一台机器充当“主机”,可以连接到各种不同的“从机”每台机器都可以在同一组电线上进行通信,使用主机发送的时钟信号来同步通信。幸运的是,就我们的目的而言,Raspberry Pi 可以通过它的一些 GPIO 引脚利用 I2C 协议,并且通信相对简单,因为只有两个设备在通信:作为主机的 Pi 和单独的从机 Wii nunchuk。

连接 Wiichuck 适配器

你可能要做的第一件事是将一组四个接头焊接到你的 Wiichuck 适配器上,如果它没有预连接它们的话。仅使用少量焊料,将接头连接到适配器(如图 12-4 所示)。与许多市售电路板一样,该适配器覆盖有一层疏焊料涂层,可防止焊料在连接之间流动并使其短路。这使得将接头焊接到连接器上成为一个简单的过程,即使对于没有经验的焊工来说也是如此。

img/323064_2_En_12_Fig4_HTML.jpg

图 12-4

焊接到 Wiichuk 适配器的接头

您将与 Wiichuck 建立四个连接—正极、负极、SDA(I2C 数据线)和 SCL(I2C 时钟线)。将正极线连接到 GPIO 引脚 1,负极线连接到 GPIO 引脚 6。将 SDA 线连接到引脚 3,将 SCL 线连接到引脚 5。在图 12-5 中,您可以看到 Wiichuk 已正确插入控制器。

img/323064_2_En_12_Fig5_HTML.jpg

图 12-5

Wiichuk 适配器的正确定位

警告

您应该将双截棍连接到 Pi 的#1 引脚(3.3V),而不是的#2 引脚(5V)。Wii 的双截棍设计为 3.3V 运行,而不是 5V。虽然它可以在 5V 电压下工作,但会严重缩短控制器的寿命。

激活 Pi 的 I2C

Raspberry Pi 有两个引脚#3 和#5,分别预配置为 I2C 协议的 SDA(数据)和 SCL(时钟)线路,因此它可以轻松地与 I2C 设备通信。Pi 还有一个 I2C 实用程序,可以查看当前连接的设备。要安装它,请键入

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

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

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

sudo i2cdetect -y 1

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

img/323064_2_En_12_Fig6_HTML.jpg

图 12-6

I2C 检测工具

在图中,没有设备出现,这是有意义的,因为我们还没有插入任何设备。但是你知道你的工具工作正常。如果您插入了 Wiichuk 适配器,您应该在#52 处看到一个条目,这是该工具的 I2C 地址。

小费

如果你很难得到结果,并且你确信你正确地连接了所有的电线和设备,检查你所有的电线是否完好无损。我花了很多时间对一个构建进行故障排除,却发现我使用的一根或多根廉价跳线在绝缘层内断裂,这意味着我在应该的地方收不到信号。这比你想象的要经常发生!

读双节棍

你现在可以开始读双节棍了。当然,最终我们会将双节棍发出的信号转化为马达的指令,但看看我们从什么信号开始可能会有指导意义。为此,让我们创建一个 Python 脚本,它导入正确的模块,监听来自双节棍的信号,并将它们输出到屏幕上。有七个信号通过导线发送:操纵杆的 x 和 y 位置,前面的“Z”和“C”按钮的状态,以及双节棍内置加速度计的 x,y 和 Z 状态。我们不会把所有这些都用在潜艇上,但是我们仍然可以看看它们。

这是你要输入的完整脚本:

#import necessary modules
import smbus
import time

bus = smbus.SMBus(1)

#initiate I2C communication by writing to the nunchuk
bus.write_byte_data(0x52,0x40,0x00)
time.sleep(0.1)

while True:
    try:
      bus.write_byte(0x52,0x00)
      time.sleep(0.1)
      data0 =  bus.read_byte(0x52)
      data1 =  bus.read_byte(0x52)
      data2 =  bus.read_byte(0x52)
      data3 =  bus.read_byte(0x52)
      data4 =  bus.read_byte(0x52)
      data5 =  bus.read_byte(0x52)
      joy_x = data0
      joy_y = data1
      accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
      accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
      accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
      buttons = data5 & 0x03

      button_c = (buttons == 1) #button_c is True if buttons = 1
      button_z = (buttons == 2) #button_z is True if buttons = 2

      print 'Jx: %s Jy: %s Ax: %s Ay: %s Az: %s Bc: %s Bz: %s' % (joy_x, joy_y, accel_x, accel_y, accel_z, button_c, button_z)
    except IOError as e:
      print e

如果你还没有,为你的潜水程序创建一个文件夹,在那个文件夹中保存这个脚本为nunchuktest. py,然后运行它。导入必要的模块后,脚本创建了一个“总线”,通过它可以与双节棍进行所有的通信。然后它通过写信给双截棍的 I2C 地址(bus.write_byte_data())开始通信。然后,它进入一个循环,连续读取来自双节棍(data0data1等)的 5 字节字符串,并按顺序将它们分类为操纵杆方向、加速度计读数和按钮按压。然后,它将这些值打印到屏幕上,并重复这个过程。

因为它涉及到对 I2C 总线的读写,所以您必须以 root 用户身份运行它,所以键入以下命令:

sudo python nunchuktest.py

脚本一启动,就会显示所有双节棍传感器的运行状态报告,实时更新,格式如下:

Jx: 130 Jy: 131 Ax: 519 Ay: 558 Az: 713 Bc: False Bz: False

在脚本运行时,尝试移动操纵杆、按下按钮并摇动双节棍来观察值的变化。您现在知道如何从双节棍读取值,我们将用它来驱动马达。

双截棍和 LED 试验方项目

作为一个小项目(也是为了测试我的双节棍阅读能力),我在一个小试验板上连接了六个 led 和一些 GPIO 引脚,这样它们就会根据我移动操纵杆或按下按钮的方式而点亮。这可能是一个值得进行的测试,以确保您不仅阅读了这些值,而且能够用这些值做一些事情。在这种情况下,选择四个 GPIO 引脚并将其设置为输出。将这些引脚连接到一个电阻器和并联连接的 led 的正极引脚(如图 12-7 所示),将所有接地连接在一起,并运行以下脚本:

img/323064_2_En_12_Fig7_HTML.jpg

图 12-7

LED 测试设置

import smbus
import time
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)

#set pins                                                              
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.OUT)
GPIO.setup (15, GPIO.OUT)
GPIO.setup (19, GPIO.OUT)
GPIO.setup (21, GPIO.OUT)
GPIO.setup (23, GPIO.OUT)

bus = smbus.SMBus(0)

bus.write_byte_data (0x52, 0x40, 0x00)
time.sleep (0.1)
while True:
    try:
        bus.write_byte (0x52, 0x00)
        time.sleep (0.1)
        data0 = bus.read_byte (0x52)
        data1 = bus.read_byte (0x52)
        data2 = bus.read_byte (0x52)
        data3 = bus.read_byte (0x52)
        data4 = bus.read_byte (0x52)
        data5 = bus.read_byte (0x52)
        joy_x = data0
        joy_y = data1
# the following lines add the necessary values to make the received 5-byte
# strings easier to decode and print
        accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
        accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
        accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
        buttons = data5 & 0x03
        button_c = (buttons == 1)
        button_z = (buttons == 2)
        print 'Jx: %s Jy: %s Ax: %s Ay: %s Az: %s Bc: %s Bz:
%s' % (joy_x, joy_y, accel_x, accel_y, accel_z, button_c, button_z)
        if joy_x > 200:
            GPIO.output (11, 1)
            GPIO.output (13, 0)
            GPIO.output (15, 0)
            GPIO.output (19, 0)
            GPIO.output (21, 0)
            GPIO.output (23, 0)
        elif joy_x < 35:
            GPIO.output (11, 0)
            GPIO.output (13, 1)
            GPIO.output (15, 0)
            GPIO.output (19, 0)
            GPIO.output (21, 0)
            GPIO.output (23, 0)
        elif joy_y > 200:
            GPIO.output (11, 0)
            GPIO.output (13, 0)
            GPIO.output (15, 1)
            GPIO.output (19, 0)
            GPIO.output (21, 0)
            GPIO.output (23, 0)
        elif joy_y < 35:
            GPIO.output (11, 0)
            GPIO.output (13, 0)
            GPIO.output (15, 0)
            GPIO.output (19, 1)
            GPIO.output (21, 0)
            GPIO.output (23, 0)
        elif button_c == True:
            GPIO.output (11, 0)
            GPIO.output (13, 0)
            GPIO.output (15, 0)
            GPIO.output (19, 0)
            GPIO.output (21, 1)
            GPIO.output (23, 0)
        elif button_z == True:
            GPIO.output (11, 0)
            GPIO.output (13, 0)
            GPIO.output (15, 0)
            GPIO.output (19, 0)
            GPIO.output (21, 0)
            GPIO.output (23, 1)
        else:
            GPIO.output (11, 0)
            GPIO.output (13, 0)
            GPIO.output (15, 0)
            GPIO.output (19, 0)
            GPIO.output (21, 0)
            GPIO.output (23, 0)
    except IOError as e:
        print e

如前所述,代码首先创建了一个“总线”,所有的通信都通过它与双节棍进行。然后,它通过写信给双截棍的 I2C 地址(bus.write_byte_data())开始通信。然后,它进入一个循环,连续读取来自双截棍的 5 字节字符串(data0data1等等),并按顺序将它们分类为操纵杆方向、加速度计读数和按钮按压。这些字符串的值指示双截棍的每个组件在做什么:例如,如果buttonZTrue,按钮被按下。同样,操纵杆的 y 方向值表示操纵杆是向前推还是向后拉。一长串if-elif语句简单地遍历接收到的值并点亮相应的 LED。

运行它(再次使用sudo)会根据你用双节棍做什么产生不同的 led 灯。

如您所见,并联 led 意味着它们共享一个公共地和一条公共高压线。相比之下,如果将它们串联,每个 LED 的正极引脚将与下一个 LED 的负极引脚相连,最后一个 LED 与 Pi 的正极引脚或负极引脚之间有一个电阻。

用双截棍控制子马达和相机

现在我们已经让双节棍工作了,我们将使用它来控制潜艇的马达,这涉及到使用 L298 马达控制器芯片。通常,我们不能用 Pi 驱动非常强大的电机或伺服系统,因为 Pi 无法提供足够的电流来驱动它们。为了绕过这一限制,我们使用电机控制器芯片,如 L298。像这样的芯片被称为 H 桥,允许你将外部电源连接到你的电机,并根据需要使用 Pi 来打开和关闭电机。L298 芯片成本仅几美元,可用于驱动难以置信的电流和电压——高达 4 安培和 46 伏。它通常用于业余爱好机器人,非常适合这种类型的应用。

然而,尽管芯片很便宜,但将其连接到 Pi 和电机可能会很复杂,因为需要使用几个 10nF 电容和一些反激二极管来保护 Pi 免受电机电压尖峰的影响。出于这个原因,我强烈推荐从 Sparkfun 购买 L298 电机驱动板,如“零件购物清单”一节所述。它使连接更简单:你插入外部电源,输入来自 Pi 的信号,输出电线到电机,你就完成了。在本章的其余部分,我将假设您使用的是 Sparkfun 板。如果你决定自己试验芯片,可以在网上找到一些不错的原理图。

为了控制潜艇和马达,我们可以修改我在前面“双节棍和 LED 测试”一节中介绍的 LED 驱动脚本,但我们不是打开和关闭 LED,而是打开和关闭马达并激活摄像机。控制将基本如下:

  • 操纵杆向前=两个电机旋转

  • 操纵杆左=右电机旋转

  • 操纵杆右=左马达旋转

  • 按下 c 按钮=用相机拍摄静态照片

  • 按下 z 按钮=用相机拍摄视频

要使用 L298 板为电机供电,需用七根电线将 Pi 连接到板上——每台连接的电机用三根,一根接地。电机 A 由 IN1、IN2 和 ENA 控制(“启用 A”)。电机 B 由 IN3、IN4 和 ENB 控制。为了控制电机 A,你设置 ENA 为“高”,然后在 IN1 或 IN2 发送电压(或者都不发送,以制动电机)。电机 B 的控制方式相同。电机的电源连接到电路板,完全绕过 Pi。要查看图示,请看表 12-1 和图 12-8 。

img/323064_2_En_12_Fig8_HTML.jpg

图 12-8

连接到 Pi 的电机和电机控制器

表 12-1

电机值和设置

| ENA 价值 | ENA = 1 | ENA = 1 | ENA = 1 | ENA = 0 | | **IN1 值** | IN1 = 1 | IN1 = 0 | IN1 = 0 | - | | **IN2 值** | IN2 = 0 | IN2 = 1 | IN2 = 0 | - | | **结果** | 电机 A 顺时针旋转 | 电机 A 逆时针旋转 | 马达 A 制动器 | 电机 A 停止 |

显然,我们需要做的就是为每个电机设置三个 GPIO 引脚。同时,我们读取设置为 I2C 输入的 GPIO 引脚,并根据我们从双节棍获得的信号将电机引脚设置为高或低。我们还将检查按钮按下,以激活相机。(图 12-7 显示了一个电机的连接,而不是两个。)

我们可以为摄像机设置每个命令(take_picturetake_video等)。)并在双节棍上按下适当的按钮时调用该函数。类似地,我们可以设置运行电机的函数,并根据操纵杆的位置调用这些函数。所有这些都发生在一个while循环中,一直持续到我们终止程序或者电池耗尽。

远程启动程序

一旦给潜水器加电,有几种方法可以让 Python 程序运行,因为您没有连接到 Pi 的键盘、鼠标或监视器。最简单的方法似乎是设置一个静态 IP 地址,从笔记本电脑远程登录到 Pi,然后从那里启动程序。然而,这只有在你登录到无线网络的情况下才能工作,而且在湖中央(或海洋,或任何你碰巧使用潜水器的地方)可能没有任何可用的网络。

您可以设置一个临时网络,在这个网络中,Pi 和您的笔记本电脑创建一个小型的专用网络,然后从您的笔记本电脑登录到 Pi。然而,建立一个自组织网络可能会有问题,如果它由于某种原因不工作,你将无法访问你的 Pi,使你的潜水器毫无用处。

经过一番思考,我决定最好的方法是在启动 Pi 时自动启动子控制程序。这样,你就可以打开它,给马达供电,然后继续和你的潜艇一起工作。

为此,我们需要做的就是编辑一个文件——您的cron调度程序。cron调度程序是一个您可以编辑的作业调度程序。它使您能够在指定的时间和间隔安排任务和脚本。要编辑它,您可以通过键入以下命令打开名为crontab的文件

sudo crontab -e

每个用户都有自己的crontab,但是通过使用sudo,我们将编辑根用户的cron,这是我们运行 Python 脚本所需的用户,因为我们正在访问 GPIO 引脚。你会看到类似图 12-9 的东西。

img/323064_2_En_12_Fig9_HTML.jpg

图 12-9

用户的crontab文件

向下滚动到文件末尾,并输入以下行:

@reboot python /home/pi/Desktop/submersible/sub.py &

这是您的 Python 脚本的具体路径(假设您已经将sub.py保存在桌面上的submersible文件夹中),并且&告诉cron在后台运行作业,以便不干扰正常的启动例程。保存文件。下次您重新启动 Pi 时,sub.py将会运行——如果您愿意,可以测试一下!

最终代码

将下面的代码保存在您的 Pi 上,最好保存在它自己的文件夹中。它也可以在 Apress 网站上以sub.py的名称获得。

import time
import smbus
from picamera import PiCamera
import RPi.GPIO as GPIO
GPIO.setwarnings (False)
GPIO.setmode (GPIO.BOARD)

camera = PiCamera()

def take_stillpic(num):
    camera.capture("image" + str(num) + "jpg")

def go_forward():
    GPIO.output (19, 1) #IN1 on
    GPIO.output (23, 0) #IN2 off
    GPIO.output (11, 1) #IN3 on
    GPIO.output (15, 0) #IN4 off

def go_backward():
    GPIO.output (19, 0) #IN1 off
    GPIO.output (23, 1) #IN2 on
    GPIO.output (11, 0) #IN3 off
    GPIO.output (15, 1) #IN4 on

def go_right():
    GPIO.output (19, 1) #IN1 on
    GPIO.output (23, 0) #IN2 off
    GPIO.output (11, 0) #IN3 off
    GPIO.output (15, 1) #IN4 on

def go_left():
    GPIO.output (19, 0) #IN1 off
    GPIO.output (23, 1) #IN2 on
    GPIO.output (11, 1) #IN3 on
    GPIO.output (15, 0) #IN4 off

#set motor control pins
#left motor
# 11 = IN3
# 13 = enableB
# 15 = IN4
GPIO.setup (11, GPIO.OUT)
GPIO.setup (13, GPIO.OUT)
GPIO.setup (15, GPIO.OUT)

#right motor
# 19 = IN1
# 21 = enableA
# 23 = IN2
GPIO.setup (19, GPIO.OUT)
GPIO.setup (21, GPIO.OUT)
GPIO.setup (23, GPIO.OUT)

#enable both motors
GPIO.output (13, 1)
GPIO.output (21, 1)

#setup nunchuk read
bus = smbus.SMBus(0)  # or a (1) if you needed used y -1 in the i2cdetect command
bus.write_byte_data (0x52, 0x40, 0x00)
time.sleep (0.5)

x = 1

while True:
    try:
        bus.write_byte (0x52, 0x00)
        time.sleep (0.1)
        data0 = bus.read_byte (0x52)
        data1 = bus.read_byte (0x52)
        data2 = bus.read_byte (0x52)
        data3 = bus.read_byte (0x52)
        data4 = bus.read_byte (0x52)
        data5 = bus.read_byte (0x52)
        joy_x = data0
        joy_y = data1
        accel_x = (data2 << 2) + ((data5 & 0x0c) >> 2)
        accel_y = (data3 << 2) + ((data5 & 0x30) >> 4)
        accel_z = (data4 << 2) + ((data5 & 0xc0) >> 6)
        buttons = data5 & 0x03
        button_c = (buttons == 1) or (buttons == 2)
        button_z = (buttons == 0) or (buttons == 2)

        if joy_x > 200: #joystick right
            go_right()
        elif joy_x < 35: #joystick left
            go_left()
        elif joy_y > 200: #joystick forward
            go_forward()
        elif joy_y < 35: #joystick back
            go_backward()
        elif button_c == True:
            x = x+1
            take_stillpic(x)
        elif button_z == True:
            print "button z! \n"
        else: #joystick at neutral, no buttons
            GPIO.output (19, 0)
            GPIO.output (23, 0)
            GPIO.output (11, 0)
            GPIO.output (15, 0)

    except IOError as e:
        print e

建造潜艇

在这一点上,我们准备开始建造真正的潜水器。把你的零件收集在一起:PVC 管和弯管、防水容器、胶水、螺丝和其他东西。请记住,我在接下来的章节中展示的设计仅仅是一个说明,而不是一个逐步说明的表格。只要你最终有一个框架,你可以在上面安装防水的 Pi 外壳和防水的电机和螺旋桨,你就成功地完成了这项任务。

注意

建筑计划,特别是电机防水程序,受到了“海鲈鱼计划”( http://www.seaperch.org )的严重影响,该计划旨在教授所有年龄段的学生建造遥控潜水器,并鼓励他们参与工程和数学领域。这是一个有价值的项目,我非常赞同它的目标。

构建框架

使用 PVC 管和 90°弯头,构建一个大致方形的框架,足够容纳您的 Pi 外壳。使用 PVC 胶水或螺丝将所有东西固定到位后,切割塑料网以适合正方形,并使用拉链将它固定在框架上。你最终会得到一个塑料“托盘”,如图 12-10 所示。

img/323064_2_En_12_Fig10_HTML.jpg

图 12-10

子平台

这是你的潜艇的尸体。我把它留得足够大,如果我需要改变浮力的话,可以在边上添加泡沫聚苯乙烯条,尽管我不应该这样做,因为码头的围栏充满了空气。

创建 Pi 的外壳

你需要一个足够大的透明塑料容器来装下你所有的电子产品。我指定“清晰”,因为你的相机将通过它看,并需要能够看到。一旦你选定了你的容器,就在上面钻三个小洞——两个用于连接马达的电线,一个用于连接双节棍的电线,如图 12-11 所示。

img/323064_2_En_12_Fig11_HTML.jpg

图 12-11

防水码头围栏

电机外壳防水

也许这个项目最难的部分是电机的防水,因为它们将在外壳之外。我用处方药瓶来装我的。首先,用绝缘胶带将电机完全包裹起来,以密封外壳上的大孔(参见图 12-12 )。

img/323064_2_En_12_Fig12_HTML.jpg

图 12-12

电机用绝缘胶带包裹

然后,剥去一小段以太网电缆的两端,将其中两根电线焊接到电机的电线上。当它完全包裹好并且电线连接好后,在药瓶上钻两个孔——一个在盖子上用于推进器,一个在底座上用于控制线。如图 12-13 和图 12-14 所示,将电线滑过瓶子,确保所有东西都紧贴在瓶子里。

img/323064_2_En_12_Fig14_HTML.jpg

图 12-14

电机紧贴安装在药瓶中

img/323064_2_En_12_Fig13_HTML.jpg

图 12-13

一种可插入药瓶的电机

现在(这是防水钥匙),把你的药瓶直立起来,在马达和电线周围装满蜡,如图 12-15 所示。石蜡,在你超市卖罐头的过道里可以买到,很适合这种情况。如果你加入一小层蜡,让它变硬,然后继续直到瓶子满了,你可能会有最好的运气。

img/323064_2_En_12_Fig15_HTML.jpg

图 12-15

电机被蜡包围

当它被蜡牢牢包围时,确保你的电机轴仍然可以转动,然后在盖子上滑动,通过你预先钻好的孔安装电机轴。然后,将你的螺旋桨滑到暴露的轴上,你应该有一个类似图 12-16 的防水电机。对潜艇另一侧的另一个马达重复上述步骤,然后用拉链将两个马达固定在潜艇的框架上。

img/323064_2_En_12_Fig16_HTML.jpg

图 12-16

防水电机,准备安装

连接双节棍

因为我们要从潜艇到你的双节棍(将保存在你的船上)运行以太网电缆,你需要使用另一段以太网电缆。剥去两端,抓住四根电线,将它们焊接到 Wiichuck 控制器上。将电缆的另一端穿过潜艇顶部的孔,然后将这四根电线的另一端连接到 Pi 的 GPIO 引脚上。

组装最终产品

一旦你的马达防水,你就可以组装最终产品了。将每台电机的电线穿过容器上的孔,并按照测试时的方式将它们连接到电机控制板。当所有电线都穿过孔后,使用船用环氧树脂密封孔。

警告

5200 密封胶极其黏糊糊的,脏兮兮的,弄在皮肤上就脱不下来。戴上手套,尽可能在室外工作。此外,不要吝啬密封胶——你要保护所有的电子设备,所以要确保水无法进入你的外壳。

当孔被密封时,确保所有的电子连接都完好无损,并将 Pi 和电机控制器放入外壳中。使用一小块胶带或海报油灰(这是我使用的)将相机压在外壳的前“墙”上,并将各种板固定到位。使用一个小试验板连接您的地,并添加您的两个电池——一个用于 Pi,一个用于电机控制器。

当你用电池给你的 Pi 供电时,你必须使用 5V 电源,因为 Pi 没有板载电压调节器。您可以尝试不同的电池组合,或者使用电压调节器来完成这项工作。在我所有的便携式 Pi 工作中,我曾破解过一个 USB 车载充电器,如图 12-17 所示,因为它能完美地提供 5V 和 2 安培的电流。

img/323064_2_En_12_Fig17_HTML.jpg

图 12-17

USB 汽车充电

打开外壳,使用 USB 转 micro-USB 线将 USB 电源输出连接到您的 Pi 电源输入。然后,将你的电池连接到充电器的电源输入端,瞧!你的 Pi 有 5V 电源!

一旦您解决了电源问题,您就可以将所有东西都放入机箱中,打开 Pi 电源,并将其密封起来。希望你已经得到了类似于图 12-18 的东西。

img/323064_2_En_12_Fig18_HTML.jpg

图 12-18

完整的潜水器

显然,图 12-18 所示的产品版本尚未完成——我还没有连接双截棍——但你可以看到电机和外壳本身的位置。作为最终的施工照片,图 12-19 显示了 Pi 的摄像机靠着外壳前墙的位置,用海报油灰固定到位。

img/323064_2_En_12_Fig19_HTML.jpg

图 12-19

Pi 摄像机在外壳中的放置

如果你已经仔细遵循了所有的说明,你现在应该有一个 Pi 驱动的潜水器,你可以从岸上或你的船上使用 Wii 双截棍进行控制。按下双节棍上的按钮就可以拍照,当你把 Pi 带上飞机时,你可以把这些照片传输到你的家用电脑上。

你能拍什么样的照片?嗯,如果你住在澳大利亚,你可以拿一些像你在图 12-20 中看到的东西。

img/323064_2_En_12_Fig20_HTML.jpg

图 12-20

水下摄影

(此处完全披露:这张照片是用 Pi 接头拍摄的而不是。但有可能是。)

然而,如果你住在阿拉斯加,你得到的照片可能最终看起来更像图 12-21 和图 12-22 。

img/323064_2_En_12_Fig22_HTML.png

图 12-22

更多阿拉斯加水下摄影

img/323064_2_En_12_Fig21_HTML.png

图 12-21

阿拉斯加水下摄影

如果你住在南加州,你可能会看到类似图 12-23 、 12-24 和 12-25 的图片。

img/323064_2_En_12_Fig25_HTML.png

图 12-25

水下照片

img/323064_2_En_12_Fig24_HTML.png

图 12-24

水下照片

img/323064_2_En_12_Fig23_HTML.png

图 12-23

水下照片

享受你的潜艇,我真的很想看看你拍的照片!

摘要

在本章中,您学习了如何使用 I2C 协议将 Wii 双节棍连接到您的 Pi,并使用它来控制 Pi 上的一些不同功能。然后,你为你的码头建造了一个防水的移动外壳,连接了一些马达和你的相机,并能够远程驾驶你的潜艇,拍摄一些(希望)令人印象深刻的水下照片。

在下一章中,您将学习如何将微控制器(Arduino)连接到您的 Pi 以增强其功能。

十三、树莓派和 Arduino

花生酱和果冻。蝙蝠侠和罗宾。哲基尔博士和海德先生。有些东西是注定要在一起的,从我们第一眼看到这个组合的那一刻起,我们就知道了。树莓派和 Arduino 就是这种情况。许多爱好者和工程师(包括我)一直在项目中使用 Arduino,但他们希望有一个尺寸类似的设备,只是功率多一点。嗯,我们的愿望随着 Pi 的出现而实现了。它比 Arduino 有更强的处理能力(Arduino 只是一个微控制器),它有一个完整的 ARM 处理器——在新的 Pi 3 中相当于四个内核。这显然是一个完美的匹配,自从 Pi 首次亮相以来,我们就一直在一起使用这两者。

当然,消费者微处理器板市场上还有 Arduino 的其他替代品。一个流行的选择是 Beagleboard,这是一种基于 ARM 处理器的板,也运行不同版本的 Linux,包括 Angstrom 甚至 Ubuntu。然而,它更像 Pi 而不是 Arduino,另一个缺点是它 100 美元的价格标签。Parallax 也推出了一些 prosumer 级的主板,如内置试验板的八处理器 Propeller,但同样,它更类似于 Pi 而不是 Arduino,截至目前,售价为 129 美元。其基本的微控制器产品线自 20 世纪 80 年代初就已出现,但从未像 Arduino 那样达到市场饱和。自 Pi 推出以来的几年里,其他各种各样的板也相继推出,取得了不同程度的成功,比如 Pine64 和 BBC 的 micro:bit(我在另一篇文章中也提到过)。

然而,这些主板中没有一个获得了接近 Arduino 的成绩。围绕这种流行的小板子,以及普通人可以用它做的不可思议的事情,一种完整的文化已经兴起。有很多很多的书籍、网站、论坛和团体致力于它的项目,所以我不会在这里重复这些来源。然而,关于 Arduino 与 Raspberry Pi 接口的信息却少之又少。Pi 是一台基于 Linux 的计算机,因此完全能够运行 Arduino 软件。在这一章中,我将带您安装该软件,并创建一两个简单的项目,这些项目只在 Pi 和 Arduino 上运行——不需要桌面计算机。

探索 Arduino

对于那些不熟悉 Arduino 的人来说,Arduino 是一种流行的微控制器技术实现,它的封装使外行人可以轻松地编程,并用一些复杂的电子设备完成复杂、有趣的任务。对于只想制作东西的人来说,这是个福音;用于 Arduino 编程的集成开发环境(IDE)几乎可以在任何计算机上运行,编程语言非常像 C 语言,而且——也许是最好的——该板价格低廉,大多数 Arduino 版本的价格不到 30 美元。

Arduino 有几个版本,从微小的 Arduino Nano 到大得多(也是最受欢迎的)的 Arduino Uno(如图 13-1 所示)和 Mega。所有的板都有一个 Atmega168 或 328 芯片作为它们的中央处理器,它们有一个串行转 USB 芯片,使它们能够与你的计算机通信。它们有一系列跳线,类似于 Pi 上的 GPIO 引脚,但大多数是母插座而不是公引脚。

img/323064_2_En_13_Fig1_HTML.jpg

图 13-1

Arduino Uno

在普通电脑上使用 Arduino 是一个简单的过程,首先从项目的主网站 http://www.arduino.cc 下载适合您电脑的 IDE 版本。安装完成后,你可以打开一个新的 Arduino 程序,称为“草图”,并立即开始与连接到主板的硬件进行交互(参见图 13-2 )。

img/323064_2_En_13_Fig2_HTML.jpg

图 13-2

典型的 Arduino 草图

在图中,您可以看到用于与伺服接口的代码;您包括了Servo.h库,创建了一个名为myservo的伺服对象,然后向该对象写入值以移动它。类似地,例如,要点亮一个 LED,您只需将一个特定的引脚设置为输出,然后向其发送“1”或“0”值,分别打开或关闭连接的 LED。正如我之前提到的,你可以看到它不是 Python 代码。行以分号结束,代码块用括号而不是缩进来描述。

Arduino 设置的另一个很好的特点是,你可以将 Atmega 芯片从电路板上取下,并在一个独立的试验项目中使用它。换句话说,假设你想在 Arduino 上设计一个电路,它将使用伺服系统和电机来打开和关闭你家的宠物门。你可以在你的 Arduino 板上编写和测试程序,然后你可以把芯片放在你的独立电路中。然后,你可以用 Atmega 的另一个芯片(成本约为 3 美元)替换板上的芯片,将 Arduino bootloader 刻录到芯片上,然后继续编程。每次设计新电路时,您不必使用整个 Arduino。

在 Pi 上安装 Arduino IDE

在 Pi 上安装 Arduino IDE 很简单,只需打开命令行并键入

sudo apt-get install arduino

系统将提示您接受所有必需的依赖项,然后 IDE 将下载并安装。

当它完成时,你需要安装pyserial——一个 Python 库,它使得通过串行接口与 Arduino 通信变得容易。打开您 Pi 的互联网浏览器(菜单栏中的地球图标)并浏览至 http://sourceforge.net/projects/pyserial/ 。点击“下载”按钮并保存文件。这是一个 gzipped tar 文件,这意味着您必须解压缩它。回到您的终端,浏览到文件的位置(/home/pi/Downloads/)并通过输入

gunzip pyserial-2.7.tar.gz
tar -xvf pyserial-2.7.tar

您将拥有一个名为pyserial-2.7的新文件夹。使用cd导航到该文件夹,并通过键入

sudo python setup.py install

注意

本节描述的过程是安装基于 Python 的库时的常规过程。setup.py是用来安装类似这样的 Python 库的常用脚本,它需要install参数才能运行。如果你正在安装一个系统范围的模块(换句话说,一个不需要你在同一个目录下运行它的模块),在它的解压文件夹中,你通常会找到一个名为build的文件夹和一个名为setup.py的文件。你不需要对build文件夹做任何事情,因为setup.py脚本为你做了一切。使用sudo(您将更改系统级文件,因此您需要以 root 用户身份执行脚本),在您的终端中键入sudo python setup.py install,脚本将安装该模块。

该库现在可供您在任何 Python 脚本中使用。

为了测试它,我们必须写一些 Arduino 代码。请耐心听我说,我将带您浏览一遍,因为这对您来说可能是新的。打开您的 Arduino IDE(如图 13-3 所示),在出现的草图窗口中,键入以下代码:

int ledPin = 13;
void setup()
{
    pinMode(ledPin, OUTPUT);
    Serial.begin(9600);
}

void loop()
{
    Serial.println("Hello, Raspberry Pi!");
    delay(1000);
}

img/323064_2_En_13_Fig3_HTML.jpg

图 13-3

在 Pi 上打开 Arduino IDE

这个脚本的第一行将变量ledPin设置为一个数字 13。下面的setup()功能将引脚 13 设置为输出,然后打开与串口的通信。最后,loop()函数(这是每个 Arduino sketch 的主程序循环)每 1000 毫秒(1 秒)将字符串"Hello, Raspberry Pi!"打印到串口。保存草图并将其命名为pi_test

现在,使用 Arduino 的 USB 电缆将 Arduino 连接到您的 Pi。请记住:虽然它是一根 USB 电缆,但 Pi 实际上将通过串行协议进行通信,因为 Arduino 有一个板载 USB 到串行转换器。当 Arduino 的绿色电源灯亮起时,您需要从 Arduino IDE 的工具菜单中的可用板中选择它(参见图 13-4 )。

img/323064_2_En_13_Fig4_HTML.jpg

图 13-4

选择您的 Arduino 主板

在你上传你刚刚写的pi-test脚本之前,最好确保你的电路板连接正确,并且你可以上传一个草图。为此,让我们运行 Arduino IDE 附带的“Blink”草图。从“文件”菜单中,选择“示例➤ 01”。基础➤眨眼(见图 13-5 )。眨眼草图将在一个新窗口中打开,这样您就不会丢失您为pi-test草图所做的工作。闪烁草图只是简单地闪烁 Arduino 的嵌入式 LED,它通常用于确保您的设置中的所有内容都配置正确。

img/323064_2_En_13_Fig5_HTML.jpg

图 13-5

加载眨眼草图

加载闪烁草图后,从“文件”菜单中选择“上传”(或单击工具栏中的向右箭头图标),等待 IDE 编译闪烁草图并将其上传到您的主板。当 Arduino 窗口显示“上传完成”时,Arduino 上的红色 LED 应该会缓慢闪烁。如果没有,请仔细检查您的连接,然后再次尝试上传。您可能会收到一条错误消息,指出找不到您选择的 COM 端口,并询问您是否要选择另一个端口。记下建议的端口(稍后您将使用该信息),选择它,并上传您的草图。

一旦你知道你的连接是正确的,切换到pi_test草图并上传到 Arduino。

编译并上传草图后,在新的终端窗口中,启动 Python 会话并键入以下内容:

>>> import serial
>>> ser = serial.Serial('/dev/ttyUSB0', 9600)

在这里,您导入了serial库,并通过 USB0 端口以 9600 波特开始通信,这是我们放在 Arduino 代码中的值。如果您必须在之前的连接协议中使用不同的端口,请使用该端口。例如,您可能需要将第二行改为

>>> ser = serial.Serial('/dev/ttyACM0', 9600)

如果您必须使用/dev/ttyACM0端口进行连接。

现在我们可以从串行设备——Arduino——读取数据,我们告诉它每秒传输一次。继续 Python 会话,输入

>>> while True:
...       ser.readline()

按两次回车键结束while循环,你的终端应该立即充满文本,如图 13-6 所示。当你厌倦了看着"Hello, Raspberry Pi\r\n"行每秒填充时,按 Ctrl+C 退出正在运行的while循环

img/323064_2_En_13_Fig6_HTML.jpg

图 13-6

从 Arduino 的串行端口读取

所以,我们现在已经确定我们可以通过串行连接从 Arduino 读取。让我们假设我们也可以用给它。返回 Arduino pi-test草图,将void loop()功能更改如下:

void loop()
{
    if (Serial.available())
    {
        flash(Serial.read()—'0');
    }
}

这段代码告诉 Arduino,如果它能够从串行连接中读取第一个接收到的整数(0),并将其作为参数发送给下面的flash()函数。完成loop()功能后,在草图中输入以下内容:

void flash(int n)
{
    for (int i = 0; i < n; i++)
    {
        digitalWrite(ledPin, HIGH);
        delay(100);
        digitalWrite(ledPin, LOW);
        delay(100);
    }

}

该函数使 Arduino 的板载 LED(硬连线至其引脚#13) n闪烁一定次数,该次数作为参数传递给它。正如你所看到的,这个概念类似于 GPIO 的输出功能;首先你声明这个管脚是一个输出,然后你写一个高或低的值来打开或关闭它。再次保存草图,并将其重新上传到 Arduino 板上。然后,回到您的终端,在同一个 Python 会话类型中

>>> ser.write('4')

你应该会得到 Arduino 的板载 LED 闪烁四次的奖励。用不同的号码试一试,确保有效。但是请记住,Arduino 被设置为只读取发送给它的第一个整数。如果你打字

>>> ser.write('10')

它会闪一次,而不是 10 次。

恭喜你!你现在可以从你的 Raspberry Pi 读写你的 Arduino 了!

运行伺服系统

不可否认的是,使用 Arduino/Pi 组合时,闪烁 LED 并不是最令人印象深刻的操作。我的主要目标是教你如何让这两个设备进行通信,可能的用途和含义由你自己决定,但是让我们来讨论如何与连接到 Arduino 的伺服系统进行通信。

事实上,我们需要做的只是稍微修改我们的 LED 代码,使之与伺服系统而不是 LED 接口。清除pi_test草图中的文本,并替换为以下内容:

#include <Servo.h>
Servo myservo;

void setup()
{
    myservo.attach(9);
    Serial.begin(9600);
}

void loop()
{
    if (Serial.available())
    {
        drive(Serial.read()—'0');
    }
    delay (1000);
}

void drive (int n)
{
    if (n < 5)
    {
        myservo.write(50);
    }
    else
    {
        myservo.write(250);
    }
}

这段代码接受您在 Python 提示符中输入的整数,根据它的值将其转换成一种或另一种速度,然后将该值作为速度写入伺服系统。为了测试它,把你的伺服系统的电源线挂在 Arduino 的 5V 管脚上,地线挂在 Arduino 的 GND 管脚上,信号线挂在 Arduino 的 9 号管脚上。然后,保存代码,将它上传到您的板上,并再次在 Python 提示符下输入不同的

>>> ser.write('5')

从 0 到 9 到提示符,看看伺服如何响应。

是的,这是一个非常简单的代码,但希望您理解其中的基本概念。通过 Pi 的串行接口与 Arduino 通信与与 GPS 单元或其他小型分支芯片通信没有什么不同。然而,Arduino 不仅更加智能,而且还具有无限的可扩展性,允许您添加 Pi 允许的任何附加部件,如果不是更多的话。

摘要

虽然本章只是简单介绍了如何将 Arduino 与您的 Raspberry Pi 接口,但我希望您现在能够认识到,与 Arduino 之类的另一块电路板进行通信,尤其是通过串行连接进行通信,是一件非常简单的事情,与其他任何设备进行通信没有什么不同。当然,主要的区别是,您可以使用它的 IDE 对 Arduino 进行编程,允许它根据 Pi 提供给它的信息进行操作。同样,Arduino 可以连接传感器,充当传感器网络集线器,并向 Pi 提供信息。这可以让您将一些处理能力卸载到 Arduino,并释放您的 Pi 用于更多处理器密集型任务。

简而言之,Arduino 和 Raspberry Pi 不是相互竞争,而是相互补充。每一个都以不同的方式完成任务,它们可以一起用于项目中的简单操作。花些时间熟悉一下这个界面——你会很高兴你这样做了。

posted @ 2024-08-09 17:42  绝不原创的飞龙  阅读(21)  评论(0编辑  收藏  举报