原文:
zh.annas-archive.org/md5/82683C8EDBA50EABD87C138CE7CE4264
译者:飞龙
前言
制造者社区中最受欢迎的小工具Raspberry Pi和最受欢迎的智能手机操作系统Android在本书中结合起来,产生了令人兴奋、有用且易于跟随的项目。所涵盖的项目在您日常与 Pi 的互动中非常有用,并且可以作为更令人惊奇的项目的构建模块。
本书涵盖内容
第一章,从任何地方远程连接到 Pi 的远程桌面,教会您如何进行初始设置,以便从世界上任何地方的 Android 设备远程连接到 Pi 桌面。
第二章,使用 Pi 进行服务器管理,在前一章的基础上管理 Pi 和我们在其上安装的不同服务器。我们甚至会介绍一个有趣、有用的项目,利用这些服务器。
第三章,从 Pi 直播监控摄像头,向您展示了如何将 Pi 变成网络摄像头,然后介绍了如何将其用于监控模式的技术,通过 Android 设备和互联网可访问。
第四章,将 Pi 变成媒体中心,向您展示如何将 Pi 变成可从 Android 设备控制的媒体中心。
第五章,使用 Pi 的未接来电,介绍了通过蓝牙从 Android 访问 Pi 上的传感器和组件所需的技术,并展示了 Pi 如何通知您手机上接收到的来电。
第六章,车载 Pi,帮助您将 Pi 连接到您的汽车,并从 Android 手机上进行跟踪。
您需要为本书准备什么
本书中使用的所有软件都可以在互联网上免费获得。您需要 Raspberry Pi 2 和一个 Android 设备。在一些章节中,我们甚至会使用 USB 无线网卡、DHT11 或 DHT22 温度传感器、跳线、LED 灯、USB 蓝牙适配器、Pi 摄像头、USB GPS 接收器和 OBD 蓝牙接口,所有这些都可以在在线商店购买。
本书的读者
Raspberry Pi Android Projects的目标是想要通过 Android 手机控制 Pi 创建引人入胜和有用的项目的读者。不需要先前对 Pi 或 Android 的知识。在每章的末尾,您将成功地创建一个可以日常使用的项目,并将具备能够帮助您将来开发更令人兴奋的项目的技能。本书涵盖的项目将包含一些较小的编程步骤,即使对于最没有经验的读者,这些步骤也将被详细描述。
约定
在本书中,您将找到一些区分不同信息类型的文本样式。以下是一些样式的示例及其含义的解释。
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名显示如下:“下一步是安装一个名为x11vnc
的组件。”
一段代码设置如下:
network={
ssid="THE ID OF THE NETWORK YOU WANT TO CONNECT"
psk="PASSWORD OF YOUR WIFI"
}
任何命令行输入或输出都以以下方式书写:
sudo apt-get install apache2
sudo apt-get install php5 libapache2-mod-php5
sudo apt-get install libapache2-mod-auth-mysql php5-mysql
新术语和重要词汇以粗体显示。您在屏幕上看到的词语,例如菜单或对话框中的词语,会以这样的方式出现在文本中:“通过按下连接按钮来建立连接,现在您应该能够在您的 Android 设备上看到 Pi 桌面了。”
注意
警告或重要提示会以这样的方式出现在一个框中。
提示
提示和技巧会以这样的方式出现。
第一章:从任何地方远程连接到您的 Pi 的远程桌面连接
在这个项目中,我们将对 Pi 和安卓平台进行简单介绍,为我们暖身。当用户希望管理 Pi 时,许多用户都会面临类似的问题。你必须靠近你的 Pi,并连接一个屏幕和一个键盘。我们将通过远程连接到我们的 Pi 桌面界面来解决这个日常问题。本章涵盖以下主题:
-
先决条件
-
在您的 Pi 上安装 Linux
-
在设置中进行必要的更改
-
在树莓派和安卓中安装必要的组件
-
连接 Pi 和安卓
先决条件
本章中将使用以下物品,并且需要完成项目:
-
树莓派 2 型 B 型号:这是树莓派家族的最新成员。它取代了以前的 Pi 1 型 B+。以前的型号应该可以很好地完成本书涵盖的项目的目的。
-
MicroSD 卡:树莓派基金会建议使用 8GB 的 6 级 MicroSD 卡。
-
安卓设备:设备至少应该有 1.5 或更高版本的安卓系统,这是本章中使用的应用所需的版本。在接下来的一些激动人心的项目中,我们将需要安卓 4.3 或更高版本。
-
HDMI 线:这将用于将 Pi 连接到屏幕进行初始设置。
-
以太网线:这将用于网络连接。
-
电脑:这将用于将 Raspbian 操作系统复制到 MicroSD 卡上。
-
USB 鼠标:这将在初始设置期间使用。
以下图片显示了树莓派 2 型 B 型号:
树莓派 2 型 B 型号
在您的 Pi 上安装 Linux
我们将在 Pi 上使用Raspbian作为操作系统。我的选择完全基于它是树莓派基金会推荐的事实。它基于 Linux 的 Debian 版本,并针对树莓派硬件进行了优化。除了是树莓派最常用的操作系统外,它还包含了几乎 35000 个软件包,如游戏、邮件服务器、办公套件、互联网浏览器等等。在我写这本书的时候,最新版本是 2015 年 5 月 5 日发布的。
安装 Raspbian 有两种主要方法。你可以使用整个操作系统镜像,也可以从一个名为NOOBS的易于使用的工具-操作系统捆绑包开始。我们将在这里涵盖这两种情况。
注意
有一些带有 NOOBS 或 Raspbian 预装的 SD 卡出售。也许买一个这样的会是个好主意,可以跳过本章的操作系统安装部分。
然而,在我们开始之前,我们可能需要格式化我们的 SD 卡,因为以前的操作系统安装可能会损坏卡。如果你使用计算机操作系统提供的格式化工具格式化了卡,但卡上只显示了一小部分可用空间,你会注意到这一点。我们将使用的工具叫做SD Formatter,可以从SD 协会的网站上下载 Mac 和 Windows 版本,网址是www.sdcard.org/downloads/formatter_4/index.html
。安装并启动它。你会看到以下界面,要求你选择 SD 卡位置:
SD Formatter 界面
使用 NOOBS 安装
最新版本的 NOOBS 可以在downloads.raspberrypi.org/NOOBS_latest
找到。下载并解压内容到 SD 卡上。将卡插入 Pi 并使用 HDMI 线连接到屏幕。不要忘记连接 USB 鼠标。当 Pi 连接到电源时,你将看到一个你可以选择的列表。在列表上选择Raspbian安装选项,然后点击安装。这将在您的 SD 卡上安装 Raspbian 并重新启动 Pi。
使用 Raspbian 镜像安装
Raspbian OS 的最新版本可以在downloads.raspberrypi.org/raspbian_latest
找到。ZIP 文件大小接近 1GB,包含一个扩展名为.img
的单个文件,大小为 3.2GB。解压内容并按照下一节中的步骤将其提取到合适的 microSD 卡中。
将 OS 映像提取到 SD 卡
要提取映像文件,我们需要一个磁盘映像实用程序,我们将在 Windows 上使用一个名为Win32 Disk Imager的免费可用实用程序。它可以在sourceforge.net/projects/win32diskimager/
上下载。在 Mac OS 上,有一个类似的工具叫做ApplePi Baker,可以在www.tweaking4all.com/hardware/raspberry-pi/macosx-apple-pi-baker/
上找到。下载并安装到您的计算机上。安装将包含一个可执行文件Win32DiskImager
,您应该右键单击它并选择以管理员身份运行。
在Win32 Disk Imager窗口中,您应该选择您提取的映像文件和 SD 卡驱动器,类似于以下截图所示:
Win32 Disk Imager 窗口
单击Write按钮将启动该过程,您的 SD 卡将准备好插入 Pi 中。
在设置中进行必要的更改
当 Pi 仍然连接到带有 HDMI 的屏幕时,使用以太网连接到网络。当 Pi 第一次启动时,您将看到一个设置实用程序,如下截图所示:
树莓派软件配置工具
您还可以选择列表中的第一个选项扩展文件系统。同时选择第三个选项启用启动到桌面。
在下一个菜单中,选择列表中的第二项以用户’pi’身份在图形桌面登录。然后,选择**并选择Yes**以重新启动设备。
在配置工具中选择桌面启动
重新启动后,您将看到 Raspbian 的默认桌面管理器环境LXDE。
在 Pi 和 Android 中安装必要的组件
如下截图所示,LXDE 桌面管理器带有初始设置和一些预装程序:
LXDE 桌面管理环境
通过点击位于顶部的选项卡栏上的屏幕图像,您将能够打开一个终端屏幕,我们将用它来向 Pi 发送命令。
下一步是安装一个名为x11vnc
的组件。这是 Linux 的窗口管理组件 X 的 VNC 服务器。在终端上输入以下命令:
sudo apt-get install x11vnc
这将下载并安装x11vnc
到 Pi。我们甚至可以设置一个密码,供 VNC 客户端远程桌面连接到这个 Pi 时使用以下命令并提供稍后使用的密码:
x11vnc –storepasswd
接下来,我们可以在 Pi 重新启动并启动 LXDE 桌面管理器时运行x11vnc
服务器。可以通过以下步骤完成:
- 进入位于
/home/pi
的 Pi 用户的.config
目录:
cd /home/pi/.config
- 在这里创建一个名为
autostart
的子目录:
mkdir autostart
- 进入
autostart
目录:
cd autostart
- 开始编辑一个名为
x11vnc.desktop
的文件。作为终端编辑器,我正在使用nano
,这是树莓派上新手用户最容易使用的编辑器,但也有更令人兴奋的替代方案,比如vi:
nano x11vnc.desktop
将以下内容添加到此文件中:
[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=X11VNC
Comment=
Exec=x11vnc -forever -usepw -display :0 -ultrafilexfer
StartupNotify=false
Terminal=false
Hidden=false
-
如果您使用nano作为您选择的编辑器,请使用(Ctrl+X, Y, Enter)保存并退出。
-
现在,您应该重新启动 Pi,使用以下命令来运行服务器:
sudo reboot
使用sudo reboot
命令重新启动后,我们现在可以通过发出ifconfig
命令在终端窗口中找出树莓派被分配的 IP 地址。分配给您的树莓派的 IP 地址可以在eth0
条目下找到,并且在inet addr
关键字之后给出。记下这个地址:
ifconfig 命令的示例输出
- 下一步是在您的 Android 设备上下载 VNC 客户端。
在本项目中,我们将使用一个名为androidVNC的免费可用的 Android 客户端,或者在 Play 商店中称为androidVNC 团队+antlersoft的VNC Viewer for Android。在撰写本书时使用的最新版本是 0.5.0。
注意
请注意,为了能够将您的 Android VNC 客户端连接到树莓派,树莓派和 Android 设备都应该连接到同一网络——Android 通过 Wi-Fi 连接,树莓派通过其以太网端口连接。
连接树莓派和 Android
在您的设备上安装并打开 androidVNC。您将看到一个要求连接详细信息的第一个活动用户界面。在这里,您应该提供连接的昵称,您在运行x11vnc
–storepasswd
命令时输入的密码,以及使用ifconfig
命令找到的树莓派的 IP 地址。通过按下连接按钮启动连接,您现在应该能够在您的 Android 设备上看到树莓派的桌面。
在 androidVNC 中,您应该能够通过点击屏幕来移动鼠标指针,并且在 androidVNC 应用程序的选项菜单下,您将找到如何使用Enter和Backspace向树莓派发送文本和按键的方法。
注意
您甚至可能会发现从另一台计算机连接到树莓派很方便。我建议在 Windows、Linux 和 Mac OS 上使用 RealVNC 来实现这一目的。
如果我想在树莓派上使用 Wi-Fi 呢?
为了在树莓派上使用 Wi-Fi dongle,首先,使用以下命令打开nano
编辑器打开wpa_supplicant
配置文件:
sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
将以下内容添加到此文件的末尾:
network={
ssid="THE ID OF THE NETWORK YOU WANT TO CONNECT"
psk="PASSWORD OF YOUR WIFI"
}
注意
我假设您已经设置了无线家庭网络以使用 WPA-PSK 作为认证机制。如果您有其他机制,您应该参考wpa_supplicant
文档。LXDE 提供了更好的通过 GUI 连接到 Wi-Fi 网络的方法。它可以在树莓派桌面环境的右上角找到。
从任何地方连接
现在,我们已经从我们的设备连接到了树莓派,我们需要连接到与树莓派相同的网络。但是,我们大多数人也希望能够从世界各地连接到树莓派。为了做到这一点,首先,我们需要知道由网络提供商分配给我们的家庭网络的 IP 地址。通过访问whatismyipaddress.com
网址,我们可以找出我们家庭网络的 IP 地址。下一步是登录到我们的路由器并打开来自世界各地对树莓派的请求。为此,我们将使用大多数现代路由器上找到的一个名为端口转发的功能。
注意
要注意端口转发中包含的风险。您正在向全世界开放对树莓派的访问权限,甚至对恶意用户也是如此。我强烈建议在执行此步骤之前更改用户pi
的默认密码。您可以使用passwd
命令更改密码。
通过登录到路由器的管理门户并导航到端口转发选项卡,我们可以打开对树莓派内部网络 IP 地址的请求,这是我们之前找到的,并且 VNC 服务器的默认端口是5900
。现在,我们可以在世界各地提供我们的外部 IP 地址给 androidVNC,而不是仅在我们与树莓派在同一网络上时才能使用的内部 IP 地址。
Netgear 路由器管理页面上的端口转发设置
注意
请参考您路由器的用户手册,了解如何更改端口转发设置。大多数路由器要求您通过以太网端口连接以访问管理门户,而不是通过 Wi-Fi。
动态局域网 IP 地址和外部 IP 地址的问题
这个设置有一个小问题。树莓派可能在每次重新启动时获得一个新的局域网 IP 地址,使得端口转发设置变得无用。为了避免这种情况,大多数路由器提供了地址保留设置。你可以告诉大多数路由器,每当连接一个具有唯一 MAC 地址的设备时,它应该获得相同的 IP 地址。
另一个问题是,您的互联网服务提供商(ISP)可能会在每次重新启动路由器或出于其他原因为您分配新的 IP 地址。您可以使用动态 DNS 服务,如 DynDNS,来避免这些问题。大多数路由器都能够使用动态 DNS 服务。或者,您可以通过联系您的 ISP 来获得一个静态 IP 地址。
摘要
在这个项目中,我们安装了 Raspbian,启动了树莓派,启用了桌面环境,并使用安卓设备连接到了树莓派。
在下一章中,我们将直接访问树莓派的控制台,甚至可以使用我们的安卓设备通过 FTP 传输文件到树莓派和从树莓派中传输文件。
第二章:使用 Pi 进行服务器管理
在这个项目的前半部分,我们将从基于桌面的控制台转移到一个基于文本的控制台,这样用户就可以获得更多的权力,并且可以执行比桌面更高级的任务。我们将从 Android 设备访问 Pi 的 Linux 控制台并远程控制它。在后半部分,我们将通过 FTP 在 Pi 和 Android 之间发送和接收文件。我们甚至将通过使用基于文本的控制台远程管理我们新安装的 FTP 服务器来结合这两部分。在本章中,我们甚至将在 Pi 上安装数据库和 Web 服务器,以展示如何以后管理它们。为了使它更有趣,我们将实现一个简单但有用的迷你项目,利用 Web 和数据库服务器。以下主题将被涵盖:
-
从 Android 远程控制台到 Pi
-
在 Pi 和 Android 之间交换文件
-
一个简单的数据库和 Web 服务器实现
-
服务器的简单管理
从 Android 远程控制台到 Pi
Linux 和 Unix 计算机的管理员多年来一直在使用称为shell的基于文本的命令行界面来管理和管理他们的服务器。由于 Pi 的操作系统 Raspbian 是 Linux 变种,因此访问并发出命令或检查 Pi 上运行的程序、服务和不同服务器的状态的最自然方式是再次通过在基于文本的 shell 上发出命令。有不同的 shell 实现,但在 Raspbian 上默认使用的是bash。在 Linux 服务器上远程访问 shell 的最著名方式是通过一般称为SSH的安全外壳协议。
注意
安全外壳(SSH)是一种加密的网络协议,用于以安全的方式向远程计算机发送外壳命令。SSH 为您做了两件事。它通过不同的工具,比如我们马上会向您介绍的工具,使您能够通过安全通道在不安全的网络上向远程计算机发送命令。
为了使 SSH 工作,应该已经有一个可以接受并响应 SSH 客户端请求的 SSH 服务器正在运行。在树莓派上,默认情况下启用了此功能。如果以任何方式禁用了它,您可以使用 Pi 配置程序通过发出以下命令来启用它:
sudo raspi-config
然后,导航到ssh并按Enter,然后选择启用或禁用 ssh 服务器。
在客户端,由于我们在本书中一直在使用 Android 作为我们的客户端,我们将下载一个名为 ConnectBot 的应用程序。这是 Android 上最受欢迎的 SSH 客户端之一,截至今天的最新版本是 1.8.4。将其下载到您的设备并打开它。
您需要提供我们在上一章中找到的用户名和 IP 地址。在这种情况下,我们不需要提供端口,因为 ConnectBot 将使用 SSH 的默认端口。如果由于主机的真实性问题而要求继续连接,请单击是。您会被问到这个问题,因为您是通过远程 SSH 首次连接到 Pi。
请注意,在以下屏幕截图中,我提供了我家庭网络的内部 IP 地址。您可能希望使用外部 IP 地址并从家庭网络外部连接到 Pi。为此,您还需要将标准 FTP 端口21
和20
添加到端口转发设置中。SSH 协议也适用,其默认端口号为22
。
注意
正如我们之前讨论过的,以这种方式打开端口存在安全风险,同时保留 Pi 用户pi
的默认密码也存在安全风险。
以下屏幕截图显示了 ConnectBot 上的连接详细信息:
ConnectBot 上的连接详细信息
现在,提供pi
账户的默认密码,即raspberry
,或者您已经更改的密码。完成此步骤后,您将可以使用 SSH 远程连接到 Pi,如下截图所示:
ConnectBot 提供的提示
您现在可以准备在 Pi 上发出命令并检查不同服务的状态。此连接将保存其所有属性。下次您想要登录时,将无需提供地址、用户名和密码信息。
注意
在 Mac 或 Linux 上,您可以使用系统默认安装的ssh
命令。在 Windows 上,您可以下载 PuTTY 来发出与 ConnectBot 中相同的命令。
在 Pi 和 Android 之间交换文件
在本章的第二部分中,我们将使用 Pi 作为 FTP 服务器,在我们的 Android 设备之间共享文件,或者将文件发送到 Pi 上,以便在连接到 Pi HDMI 端口的大屏幕上查看。我们将使用的 FTP 服务器是vsftpd
。它是许多小型项目中使用的轻量级 FTP 服务器。要在我们的 Pi 上安装它,我们使用以下命令:
sudo apt-get install vsftpd
上述命令甚至会启动 FTP 服务。
但是,我们应该在 FTP 服务器的配置中进行一些更改,以有效地使用它。为此,我们需要使用以下命令编辑 FTP 服务器配置文件:
sudo nano /etc/vsftpd.conf
找到包含#local_enable=YES
和#write_enable=YES
的两行,并在保存并退出之前删除这些行开头的#
注释符号。这些更改将使用户pi
能够登录并能够将文件发送到 Pi。要重新启动 FTP 服务器,请发出此命令:
sudo service vsftpd restart
现在,我们需要在 Android 上安装一个 FTP 客户端。为此,我们将使用AndFTP。对于我们的项目来说,使用免费版本就足够了。在打开后,我们在 Android 设备上看到以下初始视图:
AndFTP 客户端的初始视图
按下加号按钮将带您到以下视图,您将被要求提供连接属性:
AndFTP 上的连接属性
提供您在第一章中找到的 Pi 的 IP 地址,用户名为pi
,密码为raspberry
或您已更改的密码。然后,向下滚动到视图的末尾,然后按下保存按钮。这将保存连接属性并将您带回到主视图:
AndFTP 中的连接列表
单击新创建的连接,显示为蓝色文件夹,将启动到 Pi 的 FTP 连接并登录用户pi
。这将使您进入pi
用户的home
目录,如下截图所示:
用户 pi 的主目录
现在,您将能够通过在 AndFTP 中按下类似手机的图标并选择要上传的文件来从您的 Android 设备上传文件到 Pi。您可以使用同一网络上的另一台 Android 设备或甚至使用内置的 FTP 客户端在另一台计算机上设置 AndFTP,并下载新上传的文件以查看它;这样,您已经使用树莓派作为 FTP 服务器在不同的 Android 客户端之间共享了第一个文件。
一个简单的数据库和 Web 服务器实现
接下来,我们将进一步进行我们的项目,并安装数据库和 Web 服务器,稍后我们可以使用 ConnectBot 进行管理。我们甚至会更有趣,通过实施一个真实的项目来使用这些服务器。这个目的的最佳候选者是传感器测量场景。我们将把一个温度/湿度传感器连接到我们的树莓派,并将测量值保存到我们将在树莓派上安装的数据库中,一个 Web 服务器将使客户端可以访问。我们以后可以远程管理这些服务器,这是本章的主要目标。
连接传感器
为了这个项目的目的,我们将使用一个传感器DHT11,它可以测量温度和湿度,但为了更容易连接,我们将使用一个现成的模块称为Keyes DHT11或简称 DHT11,其中包含了这些传感器。
提示
甚至还有一个改进版的 DHT11,叫做 DHT22。它的成本略高,但传感器更精确。
使用这个传感器模块而不是传感器本身将使我们能够只使用三根跳线将传感器连接到树莓派,而无需面包板或电阻。使用这个模块而不是传感器的另一个优点是:传感器提供的是树莓派无法处理的模拟数据。树莓派能够处理其 GPIO 端口上的数字信息。DHT11 模块为我们进行了转换。以下图片说明了 DHT11 传感器模块以及与之相关的引脚的描述:
DHT11 传感器模块
以下图片说明了 Keyes DHT11 传感器模块:
Keyes DHT11 传感器模块
现在,将传感器模块的GND输出连接到树莓派的 GPIO 接地,5V输出连接到树莓派的 5V 引脚,DATA连接到树莓派的GPIO-4引脚。以下图示了树莓派 GPIO 引脚的布局及其名称:
树莓派 GPIO 引脚布局
下一步是读取这些传感器提供的值。为此,我们将使用Adafruit的一个广泛使用的库,这个库是专门为 Python 编程语言开发的这类传感器而设计的。在我们使用它之前,我们需要将一些软件组件安装到我们的树莓派上。
首先,我们需要使用以下命令更新我们的树莓派并安装一些依赖项:
sudo apt-get update
sudo apt-get install build-essential python-dev
传感器库本身在 GitHub 上,我们将使用以下命令从 GitHub 上下载到我们的树莓派上:
git clone https://github.com/adafruit/Adafruit_Python_DHT.git
这个命令会下载库并将其保存在一个子目录中。现在,使用以下命令进入这个子目录:
cd Adafruit_Python_DHT
接下来,您需要使用以下命令实际安装传感器库:
sudo python setup.py install
在这里,我们使用了标准的 Python 第三方模块安装功能,它会将 Adafruit 库全局安装到您的系统的标准 Python 库安装位置/usr/local/lib/python2.7/dist-packages/
。这就是为什么我们需要超级用户权限,我们可以使用sudo
命令来获得。
现在我们准备开始使用我们下载的示例代码从传感器中读取测量值。假设您仍然在Adafruit_Python_DHT
目录中,以下命令可以完成任务:
sudo ./examples/AdafruitDHT.py 11 4
在这个命令中,11
是用来识别 DHT11 传感器的描述符,4
表示 GPIO 引脚 4。您现在应该得到一个看起来像这样的输出:
Temp=25.0*C Humidity=36.0%
安装数据库
在验证传感器和连接到树莓派的连接工作正常后,我们将把测量值保存到数据库中。我们将使用的数据库是 MySQL。使用以下命令将 MySQL 安装到树莓派上:
sudo apt-get install mysql-server python-mysqldb
在安装过程中,您将被要求为管理员帐户 root 设置密码。我将 admin 设置为密码,并在即将到来的代码中引用它。以下命令将带您进入 MySQL shell,在那里您可以发出 SQL 命令,例如将数据插入数据库或查询已在数据库中的数据。当要求时,您应提供您设置的密码:
mysql -u root -p
您可以随时使用exit
命令退出 MySQL shell。
在 MySQL shell 中的下一步是创建一个数据库,并将其用于随后的任何 SQL 语句:
mysql> CREATE DATABASE measurements;
mysql> USE measurements;
以下 SQL 语句将在这个新创建的数据库中创建一个表,我们将用它来保存传感器测量值:
mysql> CREATE TABLE measurements (ttime DATETIME, temperature NUMERIC(4,1), humidity NUMERIC(4,1));
下一步是实现一个 Python 脚本,该脚本从我们的传感器中读取并将其保存到数据库中。使用先前讨论的nano
命令将以下代码放入名为sense.py
的文件中,该文件位于home
目录下。您可以使用没有参数的cd
命令从pi
目录结构中的任何位置返回到home
目录。请注意一个重要的事实,即文件不应包含任何空的前导行,这意味着引用 Python 命令的行应该是文件中的第一行。以下代码构成了我们的sense.py
文件的内容:
#!/usr/bin/python
import sys
import Adafruit_DHT
import MySQLdb
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 4)
#temperature = temperature * 1.8 + 32 # fahrenheit
print str(temperature) + " " + str(humidity)
if humidity is not None and temperature is not None:
db = MySQLdb.connect("localhost", "root", "admin", "measurements")
curs = db.cursor()
try:
sqlline = "insert into measurements values(NOW(), {0:0.1f}, {1:0.1f});".format(temperature, humidity)
curs.execute(sqlline)
curs.execute ("DELETE FROM measurements WHERE ttime < NOW() - INTERVAL 180 DAY;")
db.commit()
print "Data committed"
except MySQLdb.Error as e:
print "Error: the database is being rolled back" + str(e)
db.rollback()
else:
print "Failed to get reading. Try again!"
注意
您应该将MySQLdb.connect
方法调用中的密码参数更改为您在 MySQL 服务器上为 root 用户分配的密码。出于安全原因,您甚至应考虑创建一个仅访问measurements
表的新用户,因为 root 用户对数据库具有完全访问权限。有关此目的,请参阅 MySQL 文档。
下一步是更改文件属性,并使用以下命令将其设置为可执行文件:
chmod +x sense.py
请注意,此脚本仅保存单个测量值。我们需要安排运行此脚本。为此,我们将使用一个名为cron的内置 Linux 实用程序,它允许 cron 守护程序在后台定期运行任务。crontab,也称为 CRON TABle,是一个包含要在指定时间运行的 cron 条目的时间表文件。通过运行以下命令,我们可以编辑此表:
crontab –e
将以下行添加到此文件中并保存。这将使 cron 守护程序每五分钟运行一次我们的脚本:
*/5 * * * * sudo /home/pi/sense.py
安装 Web 服务器
现在,我们将测量结果保存到数据库中。下一步是使用 Web 服务器在 Web 浏览器中查看它们。为此,我们将使用Apache作为 Web 服务器,PHP作为编程语言。要安装 Apache 和我们的目的所需的软件包,请运行以下命令:
sudo apt-get install apache2
sudo apt-get install php5 libapache2-mod-php5
sudo apt-get install libapache2-mod-auth-mysql php5-mysql
然后,将目录更改为 Web 服务器的默认目录:
cd /var/www
在这里,我们将创建一个文件,用户可以通过我们安装的 Web 服务器访问该文件。该文件由 Web 服务器执行,并将执行结果发送给连接的客户端。我们将其命名为index.php
:
sudo nano index.php
内容应如下所示。在这里,您应该再次更改 MySQL 用户 root 的密码,以与对new mysqli
构造方法的调用中选择的密码相匹配:
<?php
// Create connection
$conn = new mysqli("localhost", "root", "admin", "measurements");
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT ttime, temperature, humidity FROM measurements WHERE ttime > NOW() - INTERVAL 3 DAY;";
$result = $conn->query($sql);
?>
<html>
<head>
<!-- Load c3.css -->
<link href="https://rawgit.com/masayuki0812/c3/master/c3.min.css" rel="stylesheet" type="text/css">
<!-- Load d3.js and c3.js -->
<script src="img/d3.min.js" charset="utf-8"></script>
<script src="img/c3.min.js"></scrip>
</head>
<body>
<div id="chart"></div>
<script>
<?php
if($result->num_rows > 0) {
?>
var json = [
<?php
while($row = $result->fetch_assoc()) {
?>{ttime:'<?=$row["ttime"]?>',temperature:<?=$row["temperature"]?> ,humidity:<?=$row["humidity"]?>},<?
}
}
?>
];
<?php
$conn->close();
?>
var chart = c3.generate({
bindto: '#chart',
data: {
x: 'ttime',
xFormat: '%Y-%m-%d %H:%M:%S',
keys: {
x:'ttime',
value: ['temperature', 'humidity']
},
json: json,
axes: {
temperature: 'y',
humidity: 'y2'
}
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d %H:%M'
}
},
y: {
label: 'temperature'
},
y2: {
show: true,
label: 'humidity'
}
}
});
</script>
</body>
</html>
我们希望此页面成为 Web 浏览器直接访问服务器时的默认起始页面。您可以按以下方式备份 Apache 的旧默认起始页面:
sudo mv index.html oldindex.html
在浏览器中导航到 Pi 的 IP 地址将在几个小时的传感器测量后产生类似以下截图的视图。在这里,我可以使用家庭网络外部的外部 IP 地址访问 Pi,因为我已将家庭路由器的端口转发设置添加到了80
的 HTTP 端口。
现在,我们有一个正在运行的 FTP、数据库和 Web 服务器。让我们使用 ConnectBot 进行管理。
服务器的简单管理
以下命令仅检查 FTP 服务器的状态:
service vsftpd status
如果 FTP 服务器出现问题,可以使用此命令重新启动:
sudo service vstfpd restart
我们使用的service
实用程序允许您使用以下两个命令重新启动数据库和 Web 服务器:
sudo service mysql restart
sudo service apache2 restart
使用以下命令检查 MySQL 服务器的状态:
mysqladmin -u root -p status
如果您认为数据库的大小已经增长太大,可以启动 MySQL 控制台并运行 SQL 查询以查看数据库大小:
mysql –u root –p
mysql> SELECT table_schema "DB", Round(Sum(data_length + index_length) / 1024 / 1024, 1) "Size in MB"
FROM information_schema.tables
GROUP BY table_schema;
甚至可以使用以下查询删除三天前的记录:
select measurements;
delete from measurements where ttime < NOW() - INTERVAL 3 DAY;
或者,作为替代方案,您可以使用 shell 命令检查文件系统的大小:
df -h
总结
本章向您介绍了将树莓派作为服务器的管理以及如何从 Android 向其发出命令。我们在树莓派上安装了 FTP 服务器,并在 Android 客户端之间共享文件。为了展示数据库和 Web 服务器的示例,我们实施了一个有用的项目,并学会了远程管理这些服务器。
下一章将向您介绍树莓派摄像头,并帮助您实现监控解决方案。
第三章:从树莓派实时流式传输监控摄像头
在本章中,我们将连接摄像头到树莓派,并从中实时流式传输视频。然后我们将能够从我们的 Android 设备观看这些内容的流式传输。这一章将使我们更接近使用树莓派,远离树莓派的管理。
在本章中,我们将涵盖以下主题:
-
硬件和软件配置
-
将视频流式传输到 Android 设备
-
监控模式
硬件和软件配置
我们将使用为树莓派开发的标准摄像头,在许多主要电子商店的价格约为 30 美元。
树莓派摄像头
树莓派有一个摄像头端口,您可以插入摄像头电缆。树莓派上的插头可以通过向上拉开来打开。如果您在连接摄像头到树莓派时遇到问题,您可以在互联网上找到许多视频来展示如何操作。您可以观看树莓派基金会的视频www.raspberrypi.org/help/camera-module-setup/
。
下一步是配置树莓派并启用摄像头硬件。这是通过发出以下命令访问的树莓派配置程序完成的:
sudo raspi-config
在提供的菜单中,选择启用摄像头并点击Enter。然后点击完成,您将被提示重新启动。
将视频流式传输到 Android 设备
从树莓派到 Android 的最简单的流式传输方式是使用RaspiCam Remote应用程序,该应用程序登录到树莓派并执行必要的命令。然后自动从树莓派获取流。下载并打开应用程序,在初始视图中提供登录详细信息,如 IP 地址、用户名和密码。请注意,默认情况下,它使用默认的登录帐户详细信息和 SSH 端口。如果您使用默认安装,则只需要 IP 地址。如果您启用端口22
的端口转发,甚至可以通过互联网访问摄像头,如第一章, 从任何地方连接到树莓派的远程桌面连接中所述。以下屏幕截图显示了 RaspiCam Remote 应用程序的登录设置:
RaspiCam Remote 应用程序的初始视图
等待几秒钟后,您应该在 Android 设备上看到树莓派摄像头拍摄的第一张照片。点击相机图标后,摄像头将开始流式传输,如下面的屏幕截图所示:
从树莓派流式传输
下一步是使用H.264设置获得更好的流式传输质量。连接到 RaspiCam Remote 应用程序后,您应该打开设置并勾选H.264复选框。但是,在再次通过应用程序连接之前,我们需要使用以下命令在树莓派上安装 VLC 服务器。您可能会时不时地遇到install
命令的问题,但再次运行它几乎总是可以解决问题:
sudo apt-get install vlc
下一步是在 Android 上安装更好的 VLC 客户端。我们将使用VLC for Android beta应用程序。安装后,再次打开 RaspiCam Remote 应用程序,然后通过单击相机图标开始流式传输。此时,Android 将要求您选择标准视频播放器或新安装的 VLC for Android beta。选择后者,您将体验到更好的流式传输质量。不要忘记在路由器的端口转发设置中添加端口8080
,以便通过互联网访问流式视频。
手动 VLC 配置
RaspiCam 远程应用程序在流视频内容之前会自动配置 Pi 上的 VLC。你们中的一些人可能想要直接从 VLC 应用程序连接到视频流,并跳过 Android 上的 RaspiCam。以下命令与你在 Android 设备上使用 RaspiCam 开始流媒体之前提供的命令相同:
/opt/vc/bin/raspivid -o - -n -t 0 -fps 25 -rot 0 -w 640 -h 480 | /usr/bin/vlc -I dummy stream:///dev/stdin --sout '#standard{access=http,mux=ts,dst=:8080}' :demux=h264 &
如果你发出上述命令,你将能够从 VLC 应用程序查看流媒体内容。你可以通过点击 VLC 应用程序操作栏上的天线图标来建立连接。它将提示输入流地址,即 http://PI_IP_ADDRESS:8080
。
监视模式
看到摄像头的视频流很酷,但能够在监视模式下运行它更有用。我们希望摄像头能够对运动做出反应,并在检测到运动时保存图像或视频,这样我们可以稍后查看它们,而不是一直盯着视频。为此,我们将开始在我们的 Pi 上安装一个运动识别软件,这个软件因为明显的原因被称为 motion
:
sudo apt-get install motion
这将安装 motion
软件,以下命令将向内核添加必要的软件包:
sudo modprobe bcm2835-v4l2
最好将其放在 /etc/rc.local
文件中,以便在启动时运行。不过,你应该将它放在 exit 0
行之前。
我们甚至会进行一些配置更改,以便能够访问 motion
提供的流媒体和控制功能。使用以下命令编辑 motion 的配置文件:
sudo nano /etc/motion/motion.conf
默认情况下,对 motion 的 Web 访问受到限制,只能从本地主机访问,这意味着你不能从 Pi 以外的其他计算机访问它。我们将通过找到 motion.conf
文件中的以下行来更改这种行为:
webcam_localhost on
control_localhost on
请注意,这些不是文件中的连续行。另外,如果你使用 nano 作为你的编辑器,你可以按 Ctrl+W 进入搜索模式。
我们将通过分别用以下代码替换前面的代码来关闭本地主机访问行为:
webcam_localhost off
control_localhost off
此外,我们希望 motion
服务在后台模式下执行,同时作为 daemon
运行。为此,我们应该在同一文件中找到以下代码行:
daemon on
我们应该用这行替换它:
daemon off
如果我们现在启动 motion
,我们将得到以下错误:
Exit motion, cannot create process id file (pid file) /var/run/motion/motion.pid: No such file or directory
为了摆脱这个错误,我们可以创建 motion
抱怨的这个文件夹:
sudo mkdir /var/run/motion
请注意,这个目录可能会在启动时被删除,所以最好将这个命令添加到 /etc/rc.local
文件中。
现在,你可以最终启动和停止你的 Pi 摄像头进入监视模式,发出以下命令,最好使用 ConnectBot 应用程序或我们在上一章中讨论过的任何其他 SSH 客户端。以下命令将启动 motion
:
sudo motion
停止运动,发出以下命令:
sudo pkill -f motion
如果你总是想在启动时运行它,我不建议这样做,因为你的 Pi 可能会存储空间不足,你应该使用以下命令编辑 /etc/default/motion
文件:
sudo nano /etc/default/motion
在这个文件中,你会找到以下行:
start_motion_daemon=no
你应该用这个替换它:
start_motion_daemon=yes
你可以使用以下命令启动服务,或者重新启动你的 Pi,这将自动启动服务:
sudo service motion start
要检查所有服务以及 motion 服务的状态,你可以使用以下命令:
sudo service --status-all
Motion 软件分为两部分。第一部分是您可以观看流视频的地方,第二部分是您可以在检测到运动时查看图像/视频文件的地方。您可以通过打开http://IP_ADRESS_OF_THE_PI:8081
网页来查看 motion 软件的流。由于某种原因,motion 软件的这一部分只能在 Firefox 中工作,但是下一节讨论的监视部分将在其他浏览器中工作。请注意,您不能同时启动 motion 服务器和 VLC 通过 RaspiCam 应用程序,因为它们使用相同的端口。以下屏幕截图显示了 motion 视频的流:
端口 8081 上的 motion 流视频
您可以使用AndFTP登录到 Pi,如前一章所述,并导航到/tmp/motion
文件夹,以查看每当检测到运动时保存的图像。重新启动 motion 服务将清空文件夹的内容。
提示
在路由器的端口转发设置中添加端口8080
、8081
和 FTP 端口21
,以便从网络外部访问这些服务。
在 Web 上访问监视图像
在几乎所有涉及监视的场景中,我们希望通过互联网访问保存的图像,这些图像是在检测到运动时保存的。为了做到这一点,我们将连接motion
保存图像的目录到我们在上一章中已经安装的 Apache 服务器上。运行以下命令将实现这一点:
sudo ln -s /tmp/motion /var/www/motion
您还应该在motion.conf
文件中添加motion
保存图像和视频的目录,使用以下行:
target_dir /tmp/motion
现在,在浏览器中打开http://IP_ADRESS_OF_THE_PI/motion
链接,您将看到motion
在摄像头前检测到运动时保存的图像列表。
请注意,如果motion
尚未检测到任何运动并创建/tmp/motion/
目录,则您可能会从 Web 浏览器中收到访问被禁止的错误。以下屏幕截图说明了 motion 保存的图像列表:
通过 Web 访问检测到运动时的图像和视频文件
摘要
我们已经从对 Pi 的管理转移到更真实的项目,并在 Pi 上安装了摄像头;因此,可以在 Android 设备和 Web 上查看 Pi 的流。我们甚至学会了如何将 Pi 用作监视摄像头,并查看其检测到的运动。
在下一章中,我们将继续在更有趣的场景中使用 Pi,并将其变成一个媒体中心。
第四章:将您的 Pi 变成媒体中心
在前几章中,我们一直在管理我们的 Pi 并实施有用的项目。在本章中,我们将更多地将我们的 Pi 用作娱乐来源,并将其变成媒体中心。涵盖的主题如下:
-
在 Pi 上安装和设置媒体中心
-
通过 Android 远程控制连接到媒体中心
-
从您的媒体中心获取更多
-
使用 NOOBS 安装媒体中心
在 Pi 上安装和设置媒体中心
我们选择用于此项目的媒体中心软件是Kodi,以前被称为 XBMC。它是开源的,被广泛使用,并有很多附加组件。
像往常一样,我们将使用apt-get
命令在我们的 Pi 上安装必要的软件:
sudo apt-get install kodi
然后,我们将运行kodi-standalone
可执行文件,这将启动 Kodi 并在 Pi 的 HDMI 端口上显示其用户界面。因此,重要的是您连接 Pi 到屏幕上使用 HDMI 端口,而不是远程桌面来查看 Kodi 的用户界面。现在,您可以连接 USB 键盘或鼠标来在 Kodi 内进行导航。
启动时启动 Kodi
我们绝对不希望运行一个命令来启动媒体中心,无论从 Android 运行命令有多容易,正如前几章中所讨论的。因此,我们需要使用crontab -e
命令在启动时启动命令。在crontab
命令打开的文件的末尾添加以下行:
@reboot /usr/bin/kodi-standalone &
现在,每当您重新启动 Pi 时,Kodi 都将自动重新启动。请注意,在这里,您通过 Pi 的 HDMI 端口访问媒体中心,但您也可以使用第一章中讨论的工具通过远程桌面访问。
通过 Android 远程控制连接到媒体中心
当前设置的主要问题是您只能使用连接的键盘或鼠标来控制媒体中心,这使得它不像媒体中心应该那样舒适。但是,有一个名为Kore的 Android 上的 Kodi 远程控制,使得远程控制媒体中心变得非常容易。您可以从 Google Play 下载它。它的官方名称是Kore, Kodi 的官方遥控器,由XBMC Foundation发布,这是一个运营 Kodi 媒体中心项目的非营利组织。
在您可以将 Android 上的远程控制应用连接到 Pi 上的 Kodi 之前,您需要在 Kodi 上进行一些设置更改。转到 Kodi 中的SYSTEM菜单,然后Settings,Services和Webserver。在这里,您应该选择允许通过 HTTP 控制 Kodi。然后转到同一菜单中的Remote control设置,并启用允许此系统上的程序控制 XBMC和允许其他系统上的程序控制 XBMC设置。现在在 Android 上打开 Kore 并让它搜索媒体中心。如果手机和媒体中心在同一网络上,Kore 应该能够找到它。搜索成功后,您将看到类似以下截图的视图:
提示
请注意,Kodi 的默认 HTTP 端口与上一章中看到的 motion 服务器的默认 HTTP 端口冲突。您应该在 Kodi 中更改端口设置,或者在更改 Kodi 设置之前停止 motion 服务器。
Kore 已找到媒体中心
现在,单击新发现的媒体中心以连接并开始远程控制。如果它无法自动识别媒体中心,您可以按Next按钮并手动输入参数。端口8080
是默认端口,如果您没有在 Kodi 内更改这些参数,则应使用默认用户名kodi
。
Kore 中的手动设置
充分利用你的媒体中心
媒体中心可以用于许多事情。例如,你可以下载插件,让你访问大量的在线内容,如 YouTube、可汗学院和 TED 演讲。
在 Android 设备上使用 Kodi 观看视频
另一件有趣的事情是,你可以使用之前讨论过的 AndFTP 应用从第二章树莓派服务器管理将手机上的视频上传到树莓派,然后使用媒体中心观看电影。你需要在树莓派上添加一个目录,将这些文件上传为 Kodi 中的媒体位置。转到视频 | 文件 | 文件,然后导航到添加视频…。在这里,你应该首先选择浏览,然后根文件系统。请注意,我们在第二章树莓派服务器管理中使用/home/pi
作为上传目标。即使在这种情况下也应该可以工作。浏览到这个位置,然后在所有三个弹出窗口上点击确定。现在你应该在 Kodi 的视频列表中看到树莓派。你甚至可以将此文件夹添加到收藏夹以便轻松访问。打开 Kore 远程控制应用程序,并浏览到视频下的pi
文件夹。当 Kodi 中的pi
文件夹被突出显示时,在 Kore 远程控制应用程序中按下属性按钮。然后使用 Kore 上的箭头向下滚动选择添加到收藏夹。
Kore 中列出选择的按钮,即属性按钮
接下来,从第二章树莓派服务器管理中打开 AndFTP,并连接到树莓派,或者选择之前保存的连接。现在你应该看到/home/pi
目录的内容,这是我们使用的用户pi
的默认位置。这是目标位置。然后,在 AndFTP 的操作栏中选择手机图像,选择手机上的视频并将其上传到 Kodi。
AndFTP 界面,从手机到树莓派选择上传位置
录制的视频通常位于DCIM/Camera
下。选择你想要上传的视频。然后,在操作栏中点击上传图标:
AndFTP 界面,从手机到树莓派开始上传
接下来,你可以在 Kodi 的视频部分中浏览到我们添加到pi
目录的 Kodi,并查看你刚刚在媒体中心上传的视频。
将 Android 显示屏流式传输到 Kodi
另一件非常有趣的事情是,你可以流式传输你的 Android 屏幕,并让 Kodi 显示这个流。为此,我们首先需要从 Google Play 下载一个应用,该应用将流式传输 Android 显示屏,并在内部网络上使用 URL 发布它。我们将用于此目的的应用称为屏幕流式传输镜像,有免费和付费版本。对于这个项目来说,下载免费版本就足够了。启动应用后,你需要关闭一些广告,并在弹出窗口上按下立即开始按钮。
屏幕流式传输镜像
在这里,您将看到流媒体发布的地址。我们现在将在树莓派上的pi
用户的home
目录下保存这个rtsp://YOUR_ANDROID_IP_ADRESS:5000/screen
链接,我们将把它保存在一个名为stream.strm
的文件中。然后,在 Kodi 中浏览到pi
目录,找到这个文件并打开它。请记住,我们已经将这个目录保存在 Kodi 的视频部分,并且也将其保存为收藏夹。现在,您应该能够看到您在 Android 设备屏幕上所做的任何事情,它连接到了 Kodi 使用的树莓派 HDMI 端口。另一个选项是通过这个通道显示 Android 相机捕获。我们使用的屏幕流镜像应用程序在 Android 通知区域有一个通知。如果您展开它,您将看到一个名为相机的选项。通过按下这个按钮,您将能够启动相机并查看相机捕获。
带有相机选项的屏幕流镜像通知
使用 NOOBS 安装媒体中心
在树莓派上安装媒体中心的另一个选项是使用 NOOBS。这样,用户可以非常容易地安装媒体中心,而不必担心与 Raspbian OS 相关的细节,就像我们在本章中所做的那样。我们已经在第一章中介绍了 NOOBS 的安装,从任何地方远程连接到您的 Pi。然而,在第一章中,从任何地方远程连接到您的 Pi,我们使用了离线安装选项。我们可以使用在线安装选项。您应该从downloads.raspberrypi.org/NOOBS_lite_latest
下载在线 NOOBS 安装程序。这个 ZIP 文件要小得多,但在开始安装之前,您需要将树莓派连接到以太网网络。将文件的内容解压到 SD 卡上,并在插入这张 SD 卡后重新启动您的树莓派。现在,您将看到一个要安装的操作系统列表。列表中还包含两个媒体中心。这些是OpenELEC和OSMC。它们都基于 Kodi,我们在本章中已经介绍过。
摘要
这一章很短,但很有趣。我们已经学会在树莓派上安装并设置了一个最广泛使用的媒体中心,并可以远程从我们的 Android 设备控制它。
在下一章中,我们将动手开始一些 Python 和 Android 编程,并利用更多的连接可能性来连接树莓派和 Android。
第五章:使用树莓派的未接来电
在本章中,我们将实施一个更加面向编程的项目,并深入研究蓝牙智能或蓝牙低功耗(BLE)编程。我们将通过蓝牙使树莓派和 Android 手机进行通信,并使用这个通道控制树莓派。本章将涵盖以下主题:
-
安装必要的组件
-
向蓝牙低功耗添加传感器服务
-
从 Android 应用程序连接
-
从您的 Android 手机发送重启命令到树莓派
-
从您的 Android 手机发送更多命令到树莓派
安装必要的组件
这个项目所需的硬件组件是一个支持 BLE 的蓝牙 USB 适配器。重要的是这个硬件支持 BLE,因为我们将专门利用蓝牙堆栈的这一部分。我们将使用由Plugable提供的一个,它在亚马逊上有售。
由 Plugable 提供的蓝牙适配器
我们已经下载的 Raspbian 发行版已经包含了对蓝牙的支持,但我们需要更新蓝牙软件包以获得更好的低功耗支持。您可以使用以下命令构建和安装更现代的蓝牙软件包版本:
sudo apt-get install libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libical-dev libreadline-dev libudev-dev libusb-dev make
mkdir -p work/bluepy
cd work/bluepy
wget https://www.kernel.org/pub/linux/bluetooth/bluez-5.33.tar.xz
tar xvf bluez-5.33.tar.xz
cd bluez-5.33
./configure --disable-systemd
make
sudo make install
make
步骤将编译树莓派所需的必要软件包,并需要大约 15 分钟才能完成。但是,您需要耐心等待,因为最终会得到一些很酷和有用的东西。请注意,撰写本书时 BlueZ 的最新版本是 5.33,您可以通过检查www.kernel.org/pub/linux/bluetooth/
上所有可用版本的列表来替换为最新版本。请注意,我们已使用--disable-systemd
选项禁用了systemd
支持,否则会导致构建错误。
前面的命令还安装了一些命令行工具,让我们能够配置和扫描蓝牙设备。以下命令列出了树莓派的 USB 端口上连接的所有组件:
lsusb
前面命令的输出如下:
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 0a5c:21e8 Broadcom Corp.
Bus 001 Device 005: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter
在我的情况下,蓝牙适配器的名称是Broadcom
。要获取有关特定设备的更多详细信息,请使用以下命令:
sudo lsusb -v -d 0a5c:
在这里,请注意0a5c
是我正在重用的蓝牙适配器地址的第一部分,仅获取有关此设备的更多信息。
hciconfig
工具将向您显示哪些设备支持蓝牙。这个命令在我的系统上输出了以下信息:
hci0: Type: BR/EDR Bus: USB
BD Address: 5C:F3:70:68:BE:42 ACL MTU: 1021:8 SCO MTU: 64:1
DOWN
RX bytes:564 acl:0 sco:0 events:29 errors:0
TX bytes:358 acl:0 sco:0 commands:29 errors:0
在这里看到,设备标记为DOWN
。我们将保持这种状态,因为我们安装的下一个工具需要它最初处于关闭状态。
注意
有一些有用的蓝牙低功耗命令,您可以使用它们来检查其他 BLE 设备。我们暂时不会使用这些命令,但是熟悉它们并检查您的 BLE 设备是否工作或可访问是一个好习惯。
我们之前使用过的hciconfig
工具可以帮助我们启动蓝牙设备。但是,如果您想继续本章的其余部分,不要这样做,因为下一个工具需要它处于关闭状态:
sudo hciconfig hci0 up
将此命令放入 crontab 中是个好主意,如前所述,使用 crontab 和“-e”选项,以便您可以使用 nano 作为编辑器,并自动安装新的 crontab。在文件末尾添加@reboot sudo hciconfig hci0 up
,然后保存并退出。
还有两个其他命令可以使用:
sudo hcitool lescan
这个命令列出了 BLE 设备。现在让我们看看以下命令:
sudo hcitool lecc 68:64:4B:0B:24:A7
这个命令测试与设备的蓝牙连接。请注意,后一个命令提供的地址是由前一个命令返回的。
我们甚至需要一个蓝牙的编程支持。我们将使用Go作为语言,Gatt包用于 Go 语言,为 Go 语言提供了对蓝牙低功耗的支持。通用属性配置文件(Gatt)是一个通用规范,用于在 BLE 链路上发送和接收小量数据,称为属性。让我们运行以下命令来安装go
语言:
cd
git clone https://go.googlesource.com/go
cd go
git checkout go1.4.1
cd src
./all.bash
在这里你可能想去拿杯咖啡,因为最后一个命令将花费大约 40 分钟的时间。在输出的末尾,你将看到go
安装程序要求你将一个二进制目录添加到你的路径中,以便轻松访问。以下命令可以实现这一点:
PATH=$PATH:/home/pi/go/bin
export PATH
export GOROOT=/home/pi/go
export GOPATH=/home/pi/gopath
提示
将这些命令放在/etc/profile
文件中是个好主意,这样你就可以在将来每次启动会话时执行它们。但是一定要将它们添加到文件的末尾。此外,即使你已经将它们放在profile
文件中,也不要忘记实际执行它们,如果你想在不重新启动的情况下继续。
然后,使用以下命令下载 Gatt 包源文件:
go get github.com/paypal/gatt
现在我们将使用以下命令启动一个简单的 BLE 服务器:
cd /home/pi/gopath/src/github.com/paypal/gatt
go build examples/server.go
sudo ./server
提示
完成本章后,你可能想使用以下命令将服务器启动命令放在crontab
中:
crontab -e
这样,每次重新启动 Pi 时,BLE 服务器都会启动。在末尾添加以下行:
@reboot sudo /home/pi/gopath/src/github.com/paypal/gatt/server
现在是时候找到我们的树莓派了,它在安卓上表现得像一个 BLE 设备。我们将使用BluePixel Technologies的BLE Scanner应用程序,它可以在 Play 商店上找到。当你启动它时,你将看到周围可用的 BLE 设备列表以及它们的地址。可以使用hciconfig
命令查看 Pi 上蓝牙适配器的地址。Gatt 服务器的默认实现将设备命名为Gopher。以下截图说明了 BLE Scanner 应用程序,显示 Pi 作为 BLE 设备:
安装必要的组件
BLE Scanner 应用程序显示 Pi 作为 BLE 设备
BLE 堆栈设计成设备支持一些用户可以连接的服务,并且每个服务可以提供读/写或通知特性,这主要是你可以写入、读取或从中获取通知的数据。在应用程序中点击设备,你将连接到 Pi 新启动的 BLE 服务器。你将看到四个服务。我们感兴趣的是称为UNKNOWN SERVICE的服务,它没有名称,因为它不是标准服务,它只是用来演示 Gatt 示例服务器。点击这个服务,你将看到这个服务提供的三个特性:READ,WRITE和Notification。你可以通过查看 BLE Scanner 应用程序上哪个按钮被启用来识别特性的类型。以下截图说明了 READ 特性:
READ 特性
向蓝牙低功耗添加传感器服务
我们将向 Gatt 的已有示例添加一个新服务。这个新服务将首先发布两个新特性:一个用于湿度,另一个用于温度测量。我们将使用我们在第二章中讨论过的技术以相同的方式读取测量值,使用 Pi 进行服务器管理。要读取这些测量值,我们将创建两个内容类似于我们在第二章中讨论的sense.py
文件的新文件。让我们在home
目录下创建两个文件,分别命名为humidity.py
和temperature.py
。temperature.py
文件的内容如下:
#!/usr/bin/python
import sys
import Adafruit_DHT
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 4)
print str(temperature)
humidity.py
文件的内容类似。唯一的区别是它打印出测量的湿度部分而不是温度:
#!/usr/bin/python
import sys
import Adafruit_DHT
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 4)
print str(humidity)
我们还需要使用以下命令将文件访问模式更改为可执行:
chmod +x temperature.py humidity.py
现在,您可以使用以下命令测试传感器测量:
sudo ./temperature.py
sudo ./humidity.py
下一步是通过蓝牙通道发布这些读数。我们将在现有的 Gatt 服务器示例中创建一个新服务。为此,您可以开始编辑/home/pi/gopath/src/github.com/paypal/gatt/examples
路径中服务器示例的server.go
源文件。您只需要在onStateChanged
函数定义中添加三行代码,放在其他服务定义之间。在以下内容中,请注意计数服务和电池服务已经存在。我们只需要添加传感器服务:
// A simple count service for demo.
s1 := service.NewCountService()
d.AddService(s1)
// A sensor service for demo.
sSensor := service.NewSensorService()
d.AddService(sSensor)
// A fake battery service for demo.
s2 := service.NewBatteryService()
d.AddService(s2)
此外,在同一文件中,更改广告新服务的行为以下代码,以便也广告新服务:
// Advertise device name and service's UUIDs.
d.AdvertiseNameAndServices("Gopher", []gatt.UUID{s1.UUID(), sSensor.UUID(), s2.UUID()})
我们还需要添加新服务的定义。以下代码应放在名为sensor.go
的文件中,放在 Gatt 示例的service
目录下,与其他服务定义文件(如count.go
和battery.go
)处于同一级别:
package service
import (
"fmt"
"log"
"os/exec"
"strings"
"github.com/paypal/gatt"
)
func NewSensorService() *gatt.Service {
s := gatt.NewService(gatt.MustParseUUID("19fc95c0-c111-11e3-9904- 0002a5d5c51b"))
s.AddCharacteristic(gatt.MustParseUUID("21fac9e0-c111-11e3-9246- 0002a5d5c51b")).HandleReadFunc(
func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
out, err := exec.Command("sh", "-c", "sudo /home/pi/temperature.py").Output()
if err != nil {
fmt.Fprintf(rsp, "error occured %s", err)
log.Println("Wrote: error %s", err)
} else {
stringout := string(out)
stringout = strings.TrimSpace(stringout)
fmt.Fprintf(rsp, stringout)
log.Println("Wrote:", stringout)
}
})
s.AddCharacteristic(gatt.MustParseUUID("31fac9e0-c111-11e3-9246- 0002a5d5c51b")).HandleReadFunc(
func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
out, err := exec.Command("sh", "-c", "sudo /home/pi/humidity.py").Output()
if err != nil {
fmt.Fprintf(rsp, "error occured %s", err)
log.Println("Wrote: error %s", err)
} else {
stringout := string(out)
stringout = strings.TrimSpace(stringout)
fmt.Fprintf(rsp, stringout)
log.Println("Wrote:", stringout)
}
})
return s
}
我们需要使用go
构建和重新运行我们的服务器代码。我们之前使用的以下命令将帮助我们做到这一点。请注意,您应该在/home/pi/gopath/src/github.com/paypal/gatt
目录中:
go build examples/server.go
sudo ./server
我们可以再次在 Android 上使用 BLE Scanner 应用程序连接到这项新服务并读取温度和湿度传感器数值。以下截图说明了 Gopher 服务:
连接到 Gopher 设备后,您应该看到具有19fc95c0-c111-11e3-9904-0002a5d5c51b
ID 的新添加服务,以及该服务的新特征,如下截图所示:
新增特征:一个用于温度,另一个用于湿度测量
按下读取按钮后,以下截图说明了温度测量的特征细节:
温度测量特征显示当前值为 27 度
从 Android 应用程序连接
我们已经使用现有的应用程序连接到了我们在树莓派上实现的 BLE 服务。这个名为 BLE Scanner 的应用程序非常通用,可以用于任何类型的 BLE 设备。但是,我们需要一个更专门的应用程序,它只读取测量值并抽象出 BLE 协议的细节,如设备扫描、服务和服务特征。在本节中,我们将实现一个 Android 应用程序来连接到树莓派的 BLE。为此,我们需要安装 Android Studio。Android Studio 是由 Google 专门为 Android 应用程序开发设计的。您可以通过访问developer.android.com/tools/studio/
了解更多信息。您可以在developer.android.com/sdk/
找到安装说明。我们将使用真实设备来测试我们的应用程序,而不是内置的模拟器。为此,您可能需要安装特定于您的 Android 手机的设备驱动程序,并对 Android Studio 安装进行配置更改。developer.android.com/tools/device.html
链接将帮助您执行这些操作。
现在,启动 Android Studio 并选择创建一个新项目。我将应用程序命名为BLEPi
,域名为example.com
。您应该选择手机和平板电脑作为表单因素,至少Android 5.0作为最低 SDK,因为该 SDK 引入了更好的 BLE 支持到 Android 系统。核心 BLE 支持实际上是在 Android 4.3 中添加的,本书网站上分发的代码文件以及本书的 GitHub 存储库也适用于 Android 4.3 和 Android 5.0。然而,为了简单和方便起见,即将介绍的代码仅适用于 Android 5.0。请注意,在安装 Android Studio 时,您应该已经下载了 Android 5.0 SDK,以便能够在创建项目向导中选择它。请查看本节中刚提到的链接,以获取有关此问题的更多详细信息。然后,选择向应用程序添加一个空白活动,并在下一步中不更改活动的名称;我们将保持其为MainActivity
。
我们将通过向AndroidManifest.xml
文件中的manifest
和application
标签之间添加蓝牙权限来开始我们的实现:
<uses-permission
android:name="android.permission.BLUETOOTH"/>
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"/>
然后,我们将开始对MainActivity.java
文件进行更改。首先进行以下类变量定义:
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bleScanner;
private BluetoothGatt bleGatt;
private static final int REQUEST_ENABLE_BT = 1;
private static final UUID UUID_Service =
UUID.fromString("19fc95c0-c111-11e3-9904-0002a5d5c51b");
private static final UUID UUID_TEMPERATURE =
UUID.fromString("21fac9e0-c111-11e3-9246-0002a5d5c51b");
private static final UUID UUID_HUMIDITY =
UUID.fromString("31fac9e0-c111-11e3-9246-0002a5d5c51b");
bluetoothAdapter
定义表示本地设备的蓝牙适配器,并允许您执行基本的蓝牙任务,比如发现其他设备和获取已发现设备的属性。bleScanner
提供了执行与蓝牙 LE 设备特定的扫描相关操作的方法,bleGatt
提供了蓝牙 GATT 功能,以实现与蓝牙智能设备的通信。我们在这里定义的 UUID 与我们之前在 Pi 上保存的sensor.go
文件中使用的 UUID 相同,用于识别新服务及其两个新特征。
提示
在 Android Studio 中,您可以使用Alt+Enter快捷键自动导入丢失的包。光标应该位于 java 文件中缺少导入的类上。或者,将光标放在类上,将鼠标指针放在上面,您将看到一个灯泡菜单。在这个菜单中,您可以选择导入类选项。
在onCreate
方法中,当应用程序第一次启动时,Android 系统会调用该方法,我们可以初始化bluetoothAdapter
:
BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
我们需要定义startScan
方法,每当我们想要启动 BLE 设备扫描时就会调用该方法。
private void startScan() {
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled())
{
Intent enableBtIntent =
new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
bleScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bleScanner != null) {
final ScanFilter scanFilter =
new ScanFilter.Builder().build();
ScanSettings settings =
new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
bleScanner.startScan(
Arrays.asList(scanFilter), settings, scanCallback);
}
}
}
在这里,我们首先检查设备上是否启用了蓝牙。如果没有,我们将显示一个消息框,让用户启用蓝牙。如果启用了,我们将获取bleScanner
的一个实例,该实例用于使用startScan
方法开始扫描。我们可以给一个回调实现名称,比如scanCallback
,每当扫描返回一些结果时就会调用该方法。现在,我们需要定义这个回调变量,如下面的代码所示:
private ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if("Gopher".equals(result.getDevice().getName())) {
Toast.makeText(MainActivity.this, "Gopher found",
Toast.LENGTH_SHORT).show();
if(bleScanner != null) {
bleScanner.stopScan(scanCallback);
}
bleGatt =
result.getDevice().connectGatt(
getApplicationContext(), false, bleGattCallback);
}
super.onScanResult(callbackType, result);
}
};
ScanCallback
实现覆盖了一个重要的方法onScanResult
,每当有新设备报告时就会调用该方法。然后我们检查设备名称是否与在 Pi 上的server.go
文件中定义的名称相同。如果是,我们可以将设备属性和连接信息保存到bleGatt
变量中。我们甚至可以使用connectGatt
方法连接到设备,并提供另一个回调实现bleGattCallback
,每当 Android 系统与设备建立连接时就会调用该方法。如果找到了我们要找的设备,我们就停止扫描。这是这个回调的定义:
private BluetoothGattCallback bleGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
gatt.discoverServices();
super.onConnectionStateChange(gatt, status, newState);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
BluetoothGattService service =
gatt.getService(UUID_Service);
BluetoothGattCharacteristic temperatureCharacteristic =
service.getCharacteristic(UUID_TEMPERATURE);
gatt.readCharacteristic(temperatureCharacteristic);
super.onServicesDiscovered(gatt, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
final String value = characteristic.getStringValue(0);
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv;
if(UUID_HUMIDITY.equals(characteristic.getUuid())) {
tv = (TextView) MainActivity.this.findViewById(
R.id.humidity_textview);
} else {
tv = (TextView) MainActivity.this.findViewById(
R.id.temperature_textview);
}
tv.setText(value);
}
});
BluetoothGattService service =
gatt.getService(UUID_Service);
readNextCharacteristic(gatt, characteristic);
super.onCharacteristicRead(gatt, characteristic, status);
}
};
在这个回调实现中,我们重写了三个在不同时间从 Android 系统调用的重要方法。每当通过蓝牙与远程设备建立连接时,将调用onConnectionStateChange
方法。在这种情况下,我们可以使用discoverServices
方法启动设备的服务发现。然后,当设备上发现服务时,将调用onServicesDiscovered
方法。在这种情况下,我们将首先读取我们在树莓派上定义的传感器服务的温度特征,使用readCharacteristic
方法。每当特征读取操作的值成功时,将调用第三个重写的方法onCharacteristicRead
,在其中我们读取下一个特征,即湿度,然后在同一方法中等待此操作成功。然后,我们轮流使用readNextCharacteristic
方法读取湿度和温度值,我们将在相同的回调实现中定义该方法。这是因为 BLE 协议不允许我们同时读取两个特征。让我们看一下以下代码:
private void readNextCharacteristic(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {
BluetoothGattService service = gatt.getService(UUID_Service);
if (UUID_HUMIDITY.equals(characteristic.getUuid())) {
BluetoothGattCharacteristic temperatureCharacteristic =
service.getCharacteristic(UUID_TEMPERATURE);
gatt.readCharacteristic(temperatureCharacteristic);
} else {
BluetoothGattCharacteristic humidityCharacteristic =
service.getCharacteristic(UUID_HUMIDITY);
gatt.readCharacteristic(humidityCharacteristic);
}
}
每当相应的读操作成功时,我们使用返回的characteristic
对象的getStringValue
方法获取测量值,然后在我们将在activity_main.xml
文件中定义的 UI 元素中显示它,如下所示:
<TextView
android:id="@+id/temperature_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
<TextView
android:id="@+id/humidity_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
为了使代码完整,我们还需要在MainActivity.java
文件中定义以下方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == REQUEST_ENABLE_BT) {
startScan();
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onResume() {
startScan();
super.onResume();
}
@Override
protected void onPause() {
if(bleScanner != null) {
bleScanner.stopScan(scanCallback);
}
if (bleGatt != null) {
bleGatt.close();
bleGatt.disconnect();
bleGatt = null;
}
super.onPause();
}
每当用户启用蓝牙时,将调用onActivityResult
方法,我们需要在这种情况下开始扫描,以及每当用户启动调用onResume
的应用程序时。如果用户关闭应用程序,可以通过onPause
方法停止蓝牙连接。
这是一个很好的机会,测试我们迄今为止实施的应用的第一个版本,并验证它是否有效。在 Android Studio 的运行菜单中选择运行应用程序,然后您将有选择安装应用程序的位置的选项。然后,您将在列表中看到连接到计算机的 Android 设备。
从您的 Android 手机向树莓派发送重启命令
到目前为止,我们一直通过 BLE 从树莓派接收数据。现在,我们将使用相同的通道向其发送命令。我们将在与我们的温度和湿度读特征相同的服务中实现一个新的写特征,这些特征是在树莓派上定义的。使用这些新特征,我们将向树莓派发送重启命令。让我们从再次编辑sensor.go文件开始,并在其末尾放入以下代码:
s.AddCharacteristic(gatt.MustParseUUID("41fac9e0-c111-11e3-9246- 0002a5d5c51b")).HandleWriteFunc(
func(r gatt.Request, data []byte) (status byte) {
log.Println("Command received")
exec.Command("sh", "-c", "sudo reboot").Output()
return gatt.StatusSuccess
})
使用以下命令构建和重新启动 BLE 服务器:
cd /home/pi/gopath/src/github.com/paypal/gatt
go build examples/server.go
sudo ./server
现在,使用 BLE Scanner 应用程序测试先前提到的特征。每当您向这些特征写入内容时,树莓派将重新启动。
下一步是在我们一直在构建的 Android 应用程序中实现这个新的重启功能。
首先,添加我们刚刚定义的新写特征的 UUID 以及控制操作顺序的变量,如下所示:
private static final UUID UUID_REBOOT =
UUID.fromString("41fac9e0-c111-11e3-9246-0002a5d5c51b");
private volatile boolean isSendReboot = false;
布尔变量isSendReboot
将用于启动写特征操作并与先前定义的读操作一起进行编排。BLE 堆栈无法处理彼此太接近的读/写操作,我们希望在上一个操作完成之前避免执行下一个操作。然后,在bleGattCallback
的onCharacteristicRead
函数中,将我们调用readNextCharacteristic
的行更改为以下代码:
if(isSendReboot) {
BluetoothGattCharacteristic rebootCharacteristic =
service.getCharacteristic(UUID_REBOOT);
rebootCharacteristic.setValue("reboot");
gatt.writeCharacteristic(rebootCharacteristic);
} else {
readNextCharacteristic(gatt, characteristic);
}
在这里,如果设置了控制变量,我们将向重启特征写入值reboot
,通过点击我们即将实现的按钮。我们可以重写bleGattCallback
中的另一个方法:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
isSendReboot = false;
readNextCharacteristic(gatt, characteristic);
super.onCharacteristicWrite(gatt, characteristic, status);
}
当我们重置控制变量并继续读取操作时,将调用此方法以确保写入特征操作成功。细心的人可能会发现这段代码存在一个小问题,即我们正在向 Pi 发送重新启动命令,但与此同时,我们还试图从位于同一设备上的蓝牙设备读取特征。当 Pi 重新启动时,这些读取将无法工作,如果我们在重新启动成功完成后不关闭并重新打开应用程序,我们的应用程序将无法重新连接。解决此问题将留给您作为练习。
实现的最后一部分是向我们的用户界面添加一个命令按钮,并将此按钮连接到MainAcitivity.java
文件中的一个方法,每当按钮被按下时都会执行。首先在activity_main.xml
文件的RelativeLayout
标签内添加以下行:
<Button
android:id="@+id/reboot_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/humidity_textview"
android:text="Reboot"
android:onClick="sendRebootCommand"
android:enabled="false"/>
在MainActivity.java
文件中定义sendRebootCommand
方法:
public void sendRebootCommand(View v) throws InterruptedException
{
isSendReboot = true;
}
当单击重新启动按钮时,此函数唯一要做的事情就是设置我们之前定义的控制变量。
您还可以在ScanCallback
类实例的onScanResult
方法中添加以下代码,以在通过蓝牙连接到树莓派时启用按钮:
if(bleGatt != null) {
MainActivity.this.findViewById(R.id.reboot_button).setEnabled(true);
}
这是一个再次测试应用程序的好地方,看看您是否可以通过 Android 设备成功重新启动 Pi。
从您的 Android 手机发送更多命令到 Pi
在上一节中,我们已经从 Android 发送了重新启动命令到 Pi。在本节中,我们将发送两个新命令。一个是点亮我们将连接到 Pi 的 LED,另一个是在 Pi 上播放声音。这些命令将在接下来的章节中被重复使用。
点亮 LED 灯
我们将首先将 LED 灯连接到 Pi 的 GPIO 端口。LED 通常带有短腿和长腿。将电阻连接到 LED 的短腿,然后将一个母头/母头跳线连接到电阻的另一端。然后将这个跳线连接到 Pi 的一个地针上。查看第二章中的模式,使用 Pi 进行服务器管理,以识别针脚。请注意,当我们将温湿度传感器连接到 Pi 时,我们已经使用了一个地针。但是,有很多地针可用。将 LED 的长腿连接到 GPIO 针脚之一。我们将选择编号为17
的针脚。您可以查看第二章中的 GPIO 端口映射图,以识别端口17
。
提示
最好选择一个电阻在 270Ω到 470Ω之间。这个电阻可以保护 LED 灯免受意外电压变化的影响。如果您选择电阻值较低的电阻,LED 将会更亮。
我们将使用一个名为wiringPi的软件实用程序来访问 GPIO 和 LED 灯。我们可以使用以下命令下载和安装它:
cd
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
这些命令已经帮助我们安装了一个名为gpio
的命令行工具,您现在可以使用它来点亮 LED 灯:
gpio -g mode 17 out
gpio -g write 17 1
您可以使用以下命令关闭它:
gpio -g write 17 0
我们需要向 BLE 服务器实现添加两个新特征:一个用于打开灯,另一个用于关闭灯。在sensor.go
文件的末尾添加以下行,并注意我们为每个新创建的特征都有新的 UUID:
s.AddCharacteristic(gatt.MustParseUUID("51fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleWriteFunc(
func(r gatt.Request, data []byte) (status byte) {
log.Println("Command received to turn on")
exec.Command("sh", "-c", "gpio -g mode 17 out").Output()
exec.Command("sh", "-c", "gpio -g write 17 1").Output()
return gatt.StatusSuccess
})
s.AddCharacteristic(gatt.MustParseUUID("61fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleWriteFunc(
func(r gatt.Request, data []byte) (status byte) {
log.Println("Command received to turn off")
exec.Command("sh", "-c", "gpio -g mode 17 out").Output()
exec.Command("sh", "-c", "gpio -g write 17 0").Output()
return gatt.StatusSuccess
})
现在,再次构建和重启 BLE 服务器。如果你已经将 BLE 服务器命令添加到 crontab 中,你可能需要重新启动树莓派。接下来,再次使用 BLE Scanner 应用连接到树莓派,并在应用程序中的特性部分使用Write按钮向这些特性写入值。你需要提供一些文本来写入,否则 BLE Scanner 应用将不会发送命令。一旦你这样做了,你就可以打开和关闭 LED 灯。
提示
在尝试使用我们正在构建的应用程序访问之前,最好在 BLE Scanner 应用中检查你已经在树莓派上添加的新特性。这样,我们就可以确保我们已经正确地在树莓派端添加了特性。
下一步是在我们的应用程序中实现这个新功能。我们可以从activity_main.xml
文件中引入两个新按钮开始:
<Button
android:id="@+id/turnon_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/reboot_button"
android:text="Turn on"
android:onClick="sendTurnOnCommand"
android:enabled="false"/>
<Button
android:id="@+id/turnoff_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/turnon_button"
android:text="Turn off"
android:onClick="sendTurnOffCommand"
android:enabled="false"/>
在MainActivity.java
中,为新特性定义新的 UUID 和控制变量:
private static final UUID UUID_TURNON =
UUID.fromString("51fac9e0-c111-11e3-9246-0002a5d5c51b");
private static final UUID UUID_TURNOFF =
UUID.fromString("61fac9e0-c111-11e3-9246-0002a5d5c51b");
private volatile boolean isSendTurnOn = false;
private volatile boolean isSendTurnOff = false;
在scanCallback
的onScanResult
方法中,在启用重启按钮后,添加以下代码以启用这两个按钮:
MainActivity.this.findViewById(R.id.turnon_button).setEnabled(true);
MainActivity.this.findViewById(R.id.turnoff_button).setEnabled(true);
在bleGattCallback
的onCharacteristicRead
方法中,为isSendReboot
的控制变量的现有检查添加新的 else-if 语句。新代码将类似于以下内容:
if(isSendReboot) {
BluetoothGattCharacteristic rebootCharacteristic =
service.getCharacteristic(UUID_REBOOT);
rebootCharacteristic.setValue("reboot");
gatt.writeCharacteristic(rebootCharacteristic);
} else if(isSendTurnOn) {
BluetoothGattCharacteristic turnOnCharacteristic =
service.getCharacteristic(UUID_TURNON);
turnOnCharacteristic.setValue("turnon");
gatt.writeCharacteristic(turnOnCharacteristic);
} else if(isSendTurnOff) {
BluetoothGattCharacteristic turnOffCharacteristic =
service.getCharacteristic(UUID_TURNOFF);
turnOffCharacteristic.setValue("turnoff");
gatt.writeCharacteristic(turnOffCharacteristic);
} else {
readNextCharacteristic(gatt, characteristic);
}
在onCharacteristicWrite
方法中,添加以下代码片段以重置控制变量:
isSendTurnOn = false;
isSendTurnOff = false;
最后,在新按钮的点击事件上添加可以调用的新函数:
public void sendTurnOnCommand(View v) throws InterruptedException
{
isSendTurnOn = true;
}
public void sendTurnOffCommand(View v) throws InterruptedException
{
isSendTurnOff = true;
}
你的应用将类似于以下截图:
应用的最终版本
点击按钮后,请耐心等待新按钮的效果,因为消息需要几秒钟才能到达树莓派,并且 LED 灯需要点亮。
在树莓派上播放声音
为了能够在树莓派上播放声音,声音模块应该在重启时加载。为了做到这一点,我们需要将声音模块的规格添加到/etc/modules
文件中。如果文件中不存在snd-bcm2835
,则需要在文件中添加这一规格。
提示
你可以使用lsmod
命令行工具查看当前加载的模块:
sudo modprobe snd_bcm2835
这个命令在不重启的情况下加载声音模块,以使/etc/modules
文件的内容生效。
我们甚至需要找到一个可以播放的音频文件,可以使用以下命令进行下载:
cd
wget http://www.freespecialeffects.co.uk/soundfx/sirens/whistle_blow_01.wav
现在你可以使用以下命令播放这个声音:
aplay whistle_blow_01.wav
提示
请注意,由于 HDMI 输出,音频通道可能会默认,你可能无法在 3.5mm 插孔上听到任何声音。在这种情况下,你可以运行以下命令将默认音频播放器设置为 3.5mm 插孔:
amixer cset numid=3 1
下一步是将新的写特性添加到sensor.go
文件中,如下所示:
s.AddCharacteristic(gatt.MustParseUUID("71fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleWriteFunc(
func(r gatt.Request, data []byte) (status byte) {
log.Println("Command received to whistle ")
exec.Command("sh", "-c", "aplay /home/pi/whistle_blow_01.wav").Output()
return gatt.StatusSuccess
})
不要忘记使用go build examples/server.go
命令构建和重启树莓派。接下来,在activity_main.xml
文件中定义一个新按钮:
<Button
android:id="@+id/whistle_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/turnoff_button"
android:text="Whistle"
android:onClick="sendWhistleCommand"
android:enabled="false"/>
在MainActivity.java
文件中为onClick
事件定义一个新的事件处理程序:
public void sendWhistleCommand(View v) throws InterruptedException
{
isSendWhistle = true;
}
接下来,将新的 UUID 和控制变量添加到同一个文件中:
private static final UUID UUID_WHISTLE =
UUID.fromString("71fac9e0-c111-11e3-9246-0002a5d5c51b");
private volatile boolean isWhistle = false;
在scanCallback
实例变量的onScanResult
方法中,在bleGatt
的 null 检查的 if 语句中启用新按钮:
MainActivity.this.findViewById(R.id.whistle_button).setEnabled(true);
在bleGattCallback
变量的onCharacteristicRead
处理程序中的新 else-if 语句中添加以下代码:
else if(isSendWhistle) {
BluetoothGattCharacteristic whistleCharacteristic =
service.getCharacteristic(UUID_WHISTLE);
whistleCharacteristic.setValue("whistle");
gatt.writeCharacteristic(whistleCharacteristic);
}
在onCharacteristicWrite
方法中添加一个新的语句以重置控制变量:
isSendWhistle = false;
现在哨声命令已经准备好从我们的应用程序中进行测试。
结合命令并在来电时获得通知
在最后一节中,我们将结合哨声和 LED 点亮命令,并在手机响铃时启动这个新命令。到目前为止,我们已经习惯了创建新特性。这里是要添加到sensor.go
文件中的新特性:
s.AddCharacteristic(gatt.MustParseUUID("81fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleWriteFunc(
func(r gatt.Request, data []byte) (status byte) {
log.Println("Command received to turn on and whistle")
exec.Command("sh", "-c", "aplay /home/pi/whistle_blow_01.wav").Output()
exec.Command("sh", "-c", "gpio -g mode 17 out").Output()
exec.Command("sh", "-c", "gpio -g write 17 1").Output()
return gatt.StatusSuccess
})
我们可以将这两个命令组合起来,以免自己从发送两个单独命令的开发细节中解脱出来。我们需要在AndroidManifest.xml
文件中获取来自 Android 系统的来电状态的新权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
我们还需要在MainActivity.java
中添加新的实例变量:
private static final UUID UUID_WHISTLE_AND_TURNON =
UUID.fromString("81fac9e0-c111-11e3-9246-0002a5d5c51b");
private volatile boolean isSendWhistleAndTurnOn = false;
然后,我们需要获取系统电话服务的实例,并将我们自己的监听器附加到它上面。在onCreate
方法中添加这两行代码:
TelephonyManager TelephonyMgr = (TelephonyManager)
getSystemService(Context.TELEPHONY_SERVICE);
TelephonyMgr.listen(new PhoneListener(),
PhoneStateListener.LISTEN_CALL_STATE);
接下来,定义一个本地的PhoneListener
类:
class PhoneListener extends PhoneStateListener {
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
Toast.makeText(getApplicationContext(), incomingNumber, Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), "CALL_STATE_RINGING", Toast.LENGTH_LONG).show();
isSendWhistleAndTurnOn = true;
break;
default:
break;
}
}
}
在这里,每当我们在手机上得到一个状态变化时,我们会检查这是否是CALL_STATE_RINGING
状态。如果是,我们可以像按钮点击事件处理程序为先前定义的命令一样设置新创建命令的控制变量。然后,我们也可以在onCharacteristic
读取方法中添加这个额外的 else-if 语句:
else if(isSendWhistleAndTurnOn) {
BluetoothGattCharacteristic whistleAndTurnOnCharacteristic =
service.getCharacteristic(UUID_WHISTLE_AND_TURNON);
whistleAndTurnOnCharacteristic.setValue("whistleturnon");
gatt.writeCharacteristic(whistleAndTurnOnCharacteristic);
}
接下来,我们将在onCharacteristicWrite
方法中重置控制变量如下:
isSendWhistleAndTurnOn = false;
现在,当您的手机响铃时,您将能够看到 LED 灯亮起并在树莓派上听到哨声。请注意,我们的应用程序需要启动并可见才能正常工作。这是由我们代码中的两个主要问题之一引起的。所有通过 BLE 与树莓派的通信实际上应该在 Android 服务中进行,电话事件需要在BroadcastReceiver
中处理,而不是在activity中。这两个实现,即树莓派通信和电话状态拦截,实际上应该与activity分开。活动实际上应该是一个 UI 组件,仅此而已。然而,我们在这里的意图是只向您展示有趣的部分,并快速粗糙地完成。这些对 Android 代码的进一步改进将留作您的练习。
总结
在本章中,我们涵盖了很多内容,从树莓派上的 BLE 实现到 Android BLE 代码的细节。我们在树莓派上玩得很开心,并提出了一个可以进一步开发的有用项目。
在下一章中,我们将学习更多的方法来利用树莓派上的 BLE 设备,并且不仅将我们的手机作为 Android 设备,还将其作为树莓派的访问点。