使用树莓派进行家庭自动化

自 2011 年推出以来,树莓派在业余爱好者和教育工作者中一直非常受欢迎。树莓派是一款信用卡大小的单板计算机,配备 Broadcom BCM 2835 SoC、256MB 至 512MB RAM、USB 端口、GPIO 引脚、以太网、HDMI 输出、摄像头接口和 SD 卡插槽。树莓派最吸引人的方面是其 35 美元的低成本和庞大的用户社区。

Pi 针对各种应用有几个预构建的镜像 (http://www.raspberrypi.org/downloads),例如基于 Debian 的 Raspbian、基于 XBMC 的 (现在称为 Kodi) RASPBMC、基于 OpenELEC 的 Plex Player、Ubuntu Core、RISC OS 等。NOOBS (全新开箱即用设置) 镜像提供了一个用户友好的菜单,用于选择和安装多个发行版,随后启动到任何已安装的操作系统。Raspbian 镜像在安装过程中包含 Wolfram 语言。

自 2011 年 2 月首次发布以来,树莓派已经修订了四次,每次都进行了升级,但保持了 35 美元的稳定价格。最新发布的 Pi (树莓派 2) 拥有 900MHz 四核 Cortex A7 和 1GB RAM。此外,微软通过其 IoT 开发者计划宣布为树莓派 2 提供免费的 Windows 10 (https://dev.windows.com/en-us/featured/raspberrypi2support)。除了其多功能特性外,这也促使像我这样的粉丝升级到树莓派 2。有了几块新的树莓派 2 板,我开始寻找一些有用的方法来利用我旧的 Pi 板。

在本文中,我简要描述了我概述的项目的要求,并解释了我决定用来构建它的各种工具。然后,我介绍了我选择的硬件以及组装零件以实现系统的方式。接下来,我继续在 Raspbian 镜像上设置开发环境,并逐步讲解代码,并将所有内容整合在一起以形成完整的系统。最后,我总结了可能的改进和技巧,这些改进和技巧将扩展 Pi 家庭自动化系统的实用性。

物联网

嵌入式设备的一个持续趋势是让所有嵌入式设备都连接到互联网。互联网的开发是为了成为一个能够承受多个节点破坏的故障安全网络。物联网 (IoT) 利用了相同的冗余。随着向 IPv6 迁移,IP 地址空间将足够大,可以容纳数万亿个设备保持连接。连接的设备也使得从任何地方控制它、接收来自各种传感器的输入以及响应事件变得非常方便。家庭中大量物联网连接的设备有可能充当一个活生生的实体,对刺激做出反应。

树莓派家庭自动化

受拥有一个有生命力的家的想法的启发,我决定做一个家庭自动化项目来控制我家客厅的灯。我的项目的目标是能够定时我家客厅的灯,并使用 Web 浏览器通过互联网远程控制它们。我还想公开一个 API,可以用来从其他设备以编程方式控制该设备。

这个项目中有趣的部分不是硬件,硬件相当简单且易于构建,而是 UI。我脑海中的 UI 将支持多个用户登录到同一台 Pi 服务器。UI 状态必须与系统的实际状态保持同步,实时指示当多个用户同时操作系统时哪些灯实际上是亮着的。除此之外,当定时器触发时,灯可能会打开或关闭。在设备(例如手机或平板电脑)上运行的 UI 可能会受到随机连接中断的影响。UI 应该能够处理这种情况并尝试重新连接到 Pi 服务器。

硬件

在概述了需求之后,我开始构建硬件。表 1 显示了我用来构建系统硬件部分的物料清单,图 1 显示了硬件系统的框图。

表 1. 物料清单
组件 数量 приблизительная цена 采购自 功能
树莓派 1 $35 Newark CPU
SD 卡 1 $25 amazon.com 用于启动 RPi
Edimax WiFi 1 $10 amazon.com 为 RPi 提供无线连接
继电器模块 1 $10 amazon.com 用于切换
扁平电缆 1 $7 amazon.com 将 RPi 接头连接到继电器模块
电源 1 $8 amazon.com 为 RPi 和继电器模块供电
延长线 9 $54 沃尔玛 为 SMPS 供电并为继电器提供插头接口
铅笔盒 1 $2 沃尔玛 用于容纳整个装置
USB 电缆 1 $5 amazon.com 为 RPi 供电
14 号线 1 6 家得宝 将继电器端子连接到墙壁插座的火线
电缆夹 1 $2 家得宝 作为应力消除

图 1. 硬件系统框图

接线很耗时但很简单。首先,通过在插座端切断延长线,将 SMPS 连接到墙壁插座。剥开电线并将它们拧入 SMPS 的螺钉端子。接下来,通过切断 USB 电缆的 A 型端并将它连接到 SMPS 的电线端,以及将 micro B 端连接到 RPi,将树莓派连接到 SMPS。从扁平电缆中剥出两条导线,并将适当的端子连接到 GND 和 JDVcc。移除连接 JDVcc 和 Vcc 的跳线。不移除此跳线会将 5v 反馈到 Pi 的 3.3v 引脚并损坏它。

现在所有端子都已接线以供电,使用更多的扁平电缆将继电器模块的 IN1-IN8 线连接到 RPi 的适当 GPIO 引脚,如图 2 所示。我在这里提供的代码是为我将 IN1-IN8 连接到 GPIO1-GPIO7 的情况编写的。如果您决定以不同的方式接线,您将需要相应地修改您的代码。

RPi 的 GPIO 引脚如图 2 所示。RPi 的 IO 端口在 3.3v 电压下工作,继电器模块在 5v 电压下工作。但是,继电器使用光耦合器与 RPi 的 GPIO 引脚隔离。光耦合器可以通过 Vcc 引脚提供 3.3v 电压。继电器模块的 Vcc 引脚可以从 Pi 的 GPIO 接头提供 3.3v 电压。确保您已移除继电器模块板上桥接 Vcc 和 JDVcc 的跳线。 JDVcc 引脚应提供 5v 电压以确保继电器正常工作。继电器模块设计为低电平有效。这意味着您必须将端子 IN1-IN8 接地才能打开继电器。

图 2. RPi 的 GPIO 引脚

警告:小心处理所有接线。触电可能是致命的!

在插头端切断剩余的延长电缆,并将电线端拧入继电器。还将墙壁插座的火线串联到继电器端子。整个装置可以放在铅笔盒或类似的东西中。提前计划好这一点,以避免必须拔掉和重新连接端子。此外,我在外壳上打的孔中添加了一些螺钉电缆夹,以起到应力消除的作用(图 3)。

图 3. 硬件设置

环境

我从 Raspbian 的全新安装开始构建我的环境。对于初始安装,您需要支持 HDMI 的显示器、USB 键盘、鼠标和有线以太网连接。您也可以选择连接 Wi-Fi 适配器。按照 http://www.raspberrypi.org/documentation/installation/installing-image 上给出的说明构建用于首次启动的 SD 卡。在首次启动期间,安装程序会设置操作系统并将镜像扩展到填充整个卡。首次启动后,您应该能够使用默认凭据(用户“pi”和密码“raspberry”)登录。

成功登录后,最好更新操作系统。Raspbian 镜像基于 Debian,并使用 aptitude 包管理器。您还需要 pythonpipgit。我还建议安装 Webmin 以简化管理流程。有关安装 Webmin 的说明,请访问 http://www.webmin.com/deb.html(按照“使用 Webmin APT 存储库”部分中的说明进行操作)


sudo apt-get update && sudo apt-get dist-upgrade
sudo apt-get install python python-pip git git-core

接下来,您需要设置 Wi-Fi 连接。您可以在 http://www.raspberrypi.org/documentation/configuration/wireless 找到详细说明。我推荐 wicd-curses 选项。此时,您可以使用 sudo raspi-config 命令更改 RPi 设置。这将弹出一个 GUI,让您选择诸如与 GPU 共享的 RAM 量、超频、GUI 启动等选项。

另一个有用的工具是 Cloud 9 IDE。Cloud9 IDE 允许您使用 Web 浏览器在 RPi 上编辑代码。它还在浏览器中为您提供了一个 shell 界面。您可以开发和执行所有代码而无需离开 Web 浏览器。Cloud 9 IDE 需要特定版本的 NodeJS。使用错误的版本会导致 Cloud 9 服务器频繁崩溃,从而导致持续的挫败感。有关在树莓派上安装 NodeJS 的说明,请访问 http://weworkweplay.com/play/raspberry-pi-nodejs

软件

我决定使用 HTML5、CSS3 和 JavaScript 构建我的前端 UI。这三者的组合形成了构建 UI 的强大工具。JavaScript 提供了与服务器轻松通信的 API。还有很多 JavaScript 库可供选择,例如 JQuery、Bootstrap 等。HTML5 支持 WebSocket API,该 API 允许浏览器保持连接活动状态并通过此连接接收通信。这使得 WebSocket 对于实现实时和流媒体应用程序非常有用,例如游戏和聊天界面。CSS 用于设置各种 HTML 元素的样式。如果使用得当,它可以通过响应事件切换元素上的样式来构建动态 UI。对于本项目,我选择 JQuery 来处理事件,Bootstrap CSS 以网格形式布局按钮,并使用纯 JavaScript 来处理 WebSocket 通信。

树莓派上的后端服务器需要控制树莓派板上的 GPIO 引脚。它还需要一个 HTTP 接口来服务 UI 和一个 WebSocket 接口来传递命令和状态消息。没有现成的部署可用的此类特定服务器,所以我决定使用 Python 编写自己的服务器。Python 具有用于树莓派 GPIO、HTTP 服务器和 WebSockets 的预构建模块。由于这些模块是专门的,因此我只需要编写最少的代码。

但是,这些模块不是 Python 的一部分,需要单独安装。首先,您需要能够控制 RPi 的 GPIO 引脚。从 Python 执行此操作的最简单方法是使用来自 https://pypi.python.org/pypi/RPi.GPIO 的 RPi.GPIO 库。使用以下命令安装此模块


sudo pip install RPi.GPIO

使用 RPi.GPIO 模块非常简单。您可以在 http://sourceforge.net/p/raspberry-gpio-python/wiki/Examples 找到其用法的示例。使用该模块的第一步是将它导入到代码中。接下来,您需要选择模式。模式可以是 GPIO.BOARD 或 GPIO.BCM。该模式决定了后续命令中的引脚编号引用将基于 BCM 芯片还是板上的 IO 引脚。接下来是将引脚设置为输入或输出。现在您可以根据需要使用 IO 引脚。最后,您需要清理以释放 GPIO 引脚。清单 1 显示了使用 RPi.GPIO 模块的示例。

清单 1. 使用 RPi.GPIO 模块

import RPi.GPIO as GPIO          # import module
GPIO.setmode(GPIO.BOARD)         # use board pin numbering
GPIO.setup(0, GPIO.IN)           # set ch0 as input
GPIO.setup(1, GPIO.OUT)          # set ch1 as output
var1=GPIO.input(0)                       # read ch0
GPIO.output(1, GPIO.HIGH)        # take ch1 to high state
GPIO.cleanup()                           # release GPIO.

CherryPy 是 Python 的 Web 框架模块。它很容易扩展以使用 ws4py 模块支持 WebSocket。CherryPy 和 ws4py 也可以使用 pip 安装


pip install cherrypy
pip install ws4py

可以在 CherryPy 文档ws4py 文档 中找到使用 CherryPy 框架和 ws4py 插件的示例。可以使用清单 2 中显示的代码生成基本的 CherryPy 服务器。

清单 2. 生成基本的 CherryPy 服务器

# From the CherryPy Docs at
# https://cherrypy.readthedocs.org/en/latest/tutorials.html

import cherrypy    # import the cherrypy module

class HelloWorld(object):      #
    @cherrypy.expose           # Make the function available
    def index(self):           # Create a function for each request
        return "Hello world!"  # Returned value is sent to the browser

if __name__ == '__main__':
   cherrypy.quickstart(HelloWorld())    # start the CherryPy server 
                                        # and pass the class handle
                                        # to handle request

稍微高级的代码会将带有配置的对象传递给 quickstart 方法。清单 3 中的部分代码说明了这一点。此代码为来自 js 文件夹的 /js 请求提供服务。js 文件夹位于服务器代码的主目录中。

清单 3. 传递 quickstart 方法

cherrypy.quickstart(HelloWorld(), '', config={
   '/js': {          # Configure how to serve requests for /js
   'tools.staticdir.on': True,     # Serve content statically 
                                   # from a directory
   'tools.staticdir.dir': 'js'     # Directory with respect to 
                                   # server home.
   }
});

要向 CherryPy 服务器添加 WebSocket 支持,请按清单 4 所示修改代码。WebSocket 处理程序类需要实现三个方法:openedclosedreceived_message。清单 4 是一个基本的 WebSocket 服务器,为了解释代码的主要功能部分而保持小巧;因此,它实际上并没有做任何事情。

清单 4. 基本 WebSocket 服务器

import cherrypy                 # Import CherryPy server module
# Import plugin modules for CherryPy
from ws4py.server.cherrypyserver  import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket   # Import modules for 
                                        # the ws4py plugin.
from ws4py.messaging import TextMessage

class ChatWebSocketHandler(WebSocket):
        def received_message(self, m):
                msg=m.data.decode("utf-8")
                print msg
                cherrypy.engine.publish('websocket-broadcast', 
                 ↪"Broadcast Message: Received a message")

        def closed(self, code, reason="A client left the room 
         ↪without a proper explanation."):
                cherrypy.engine.publish('websocket-broadcast', 
                 ↪TextMessage(reason))

class Root(object):
    @cherrypy.expose
    def index(self):
        return "index"

    @cherrypy.expose
    def ws(self):
        print "Handler created: %s" % repr(cherrypy.request.ws_handler)


if __name__ == '__main__':
    WebSocketPlugin(cherrypy.engine).subscribe()   # initialize websocket
                                                   # plugin
    cherrypy.tools.websocket = WebSocketTool()          #
    cherrypy.config.update({'server.socket_host': '0.0.0.0',
        'server.socket_port': 9003,
        'tools.staticdir.root': '/home/pi'})
    cherrypy.quickstart(Root(), '', config={
             '/ws': {
                     'tools.websocket.on': True,
                     'tools.websocket.handler_cls': ChatWebSocketHandler
               }
        });

在客户端,HTML 需要实现一个连接到 WebSocket 并处理传入消息的函数。清单 5 显示了可以做到这一点的简单 HTML。此代码使用 jQuery.ready() 事件开始连接到 WebSocket 服务器。此清单中的代码实现了处理所有事件的方法:onopen()onclose()onerror()onmessage()。要扩展此示例,请将代码添加到 onmessage() 方法以处理消息。

清单 5. 连接到 WebSocket 并处理传入消息

<html>
    <head></head>
    <body>

    <script src="/js/jquery.min.js"></script>
    <script type="text/javascript">
    var ws;
    var addr="ws://127.0.0.1:9000";
    $(document).ready(function (){
            connectWS();
    });
    function dbg(m){
            console.log(m);
    }
    function connectWS(){
            dbg('Connecting...');
            if (window.WebSocket) {
                    ws = new WebSocket(addr);
            }
            else if (window.MozWebSocket) {
                    ws = MozWebSocket(addr);
            }
            else {
                    alert('Your archaic browser does not support
                     ↪WebSockets.');
                    dbg('WebSocket Not Supported');
                    return;
            }

            /* on websocket close */
            ws.onclose=function(){
                    dbg('Connection Closed.');
                    reconnect=setTimeout(connectWS,6000); //try to 
                                                          //reconnect
                                                          //every 6 secs.
            }

            /* on websocket connection */
            ws.onopen=function(){
                    dbg('Connected.');
                    ws.send('Some message to send to the 
                     ↪WebSocket server.');
            }

            /* on websocket error */
            ws.onerror=function(e){
                    dbg("Socket error: " + e.data);
            }

            /* on websocket receiving a message */
            ws.onmessage = function (evt) {
                    dbg(evt.data);
                    //add functions to handle messages.
            }
            return 0;
    }
    </script>
    </body>
</html>
Pi 家庭自动化

现在您已经了解了 WebSockets、CherryPy 和 HTML 前端的基础知识,让我们开始编写实际代码。您可以从 Git 存储库 https://bitbucket.org/lordloh/pi-home-automation 获取代码。您可以将此存储库克隆到本地 RPi 上,并使用命令开箱即用地执行它


git clone https://bitbucket.org/lordloh/pi-home-automation.git
git fetch && git checkout LinuxJournal2015May
cd pi-home-automation
python relay.py

relayLabel.json 文件保存了所需的配置,例如继电器的标签、灯的开启和关闭时间等等。清单 6 显示了配置的基本模式。为每个继电器重复此模式。dow 属性通过为一周中的每一天使用一位来形成,从星期一的 LSB 到星期日的 MSB。

清单 6. 配置的基本模式

{
  "relay1": {
    "times": [
      {
        "start": [
          <hour>,
          <minute>,
          <second>
        ],
        "end": [
          <hour>,
          <minute>,
          <second>
        ],
        "dow":
<Monday<<0|Tuesday<<1|Wednesday<<2|Thursday<<3|
↪Friday<<4|Saturday<<5|Sunday<<6>
      }
    ],
    "id": 1,
    "label": "<Appliance Name>"
  }
}

图 4 显示了系统的框图,显示了主要功能部分。表 2 枚举了客户端可以发送到服务器的所有命令以及服务器预期采取的操作。这些命令以 JSON 格式从浏览器发送到服务器。命令模式如下


{
    "c":"<command form TABLE 2>",
    "r":<relay Number>
}

updateupdateLabels 命令不接受继电器编号。除了 relay.py 和 relayLabel.json 之外,唯一需要的另一个文件是 index.html。relay.py 脚本读取此文件并在响应 HTTP 请求时提供它。index.html 文件包含用于呈现 UI 的 HTML、CSS 和 JavaScript。

图 4. 系统框图

表 2. 命令
命令 描述
on 打开继电器
off 关闭继电器
update 发送 GPIO 引脚和继电器标签的状态
updateLabels 将新标签保存到 JSON 文件

一旦系统启动并运行,您就需要通过互联网访问它。为此,您需要在本地网络上为树莓派设置永久 MAC 地址和保留 IP 地址,并在路由器上设置端口转发。执行此操作的过程因路由器而异,您的路由器手册是最好的参考。此外,您可以使用动态域名服务,这样您就不需要每次都键入您的 IP 地址来访问您的 Pi。一些路由器包括对某些动态 DNS 服务的支持。

结论

我希望本文能帮助您构建此项目或其他类似项目。此项目可以扩展以添加新功能,例如检测您的手机是否连接到您的 Wi-Fi 并打开灯。您还可以将此与应用程序集成,例如 OnX 和 Android Tasker。为网络外访问添加密码保护是有益的。请随时在 http://code.lohray.com/pi-home-automation/issues 提及任何问题、错误和功能请求。

加载 Disqus 评论