Android Things 教程:使用 Android Things 和 Google Assistant 打造智能家居系统

Google Home 是一个不错的设备,由 Google Assistant 提供支持,为 Google 提供会话界面。提出问题或告诉它做什么,它会让你的生活在家里更轻松。如果您拥有一些兼容设备,例如 Nest 恒温器或某些 Philips Hue 灯,则它们可以无缝连接,让您用声音控制这些设备。

> “Hey Google, dim the lights by 10%”(“嘿谷歌,把灯光调暗10%”)

很酷,是吧?但是如果您不拥有任何兼容设备,甚至不想创建可由 Google 智能助理控制的设备? 好消息是,可以通过创建自己的应用程序来创建 “在Google上执行操作”,从而扩展助手。要启动这些应用程序,请说 “Ok Google,与 [您的应用程序的名称] 对话”。

> 用户:“OK Google,跟 Domino 谈话” > 家:“当然,这是 Domino” > 家:[语音变化]“嗨,这是 Domino,我可以帮助你,今天你想要什么?”

因此,您可以为 Google 智能助理创建自己的应用,以控制您的任何设备

> 用户:“Hey Google,与”我的个人家庭应用程序“ 对话” > 家:“当然,这是 ”我的个人家庭应用程序“” > 家:[语音变化]“你想做什么? > 用户:“打开灯”

这里存在一个巨大的问题:我们浪费太多时间做一个简单的行动。希望我们也可以这样说:

> “Hey Google,与 ”我的私人家庭应用程序“ 对话来打开灯”

但是,严重的是,谁愿意谈这么久,只是打开一些灯?如果家庭自动化是由 Google 智能助理本身直接完成的,而不是使用单独的应用程序,则会更好。完全像你可以用兼容的智能家居设备做什么,所以你可以简单地说:

> “Hey Google,打开灯”

甚至可以用于你自己的智能设备。你猜怎么了?这正是您使用 Google 智能助理所能做的。毕竟,助手应该是推动物联网设备智能交互的中心。

在这篇文章中,我们将创建自己的智能设备并编写一些功能,以便 Google 智能助理可以直接控制它。该设备将由一个电风扇和一个 LED 条组成,均由 Android Things 提供支持。

以下是我们将实现的一个视频,使用 Google Home 来控制我们自己的智能设备:https://www.youtube.com/watch?v=62WuFN4XbkA

开始

本文概述了您需要遵循的所有步骤,以创建由 Google 智能助理控制的自己的智能设备。但是,这并不完全。 如果在阅读文章之后,想要开始创建智能家居设备,请邀请您查看这两个宝贵的资源:

创建物理设备

首先,让我们来创建我们的智能设备。

该设备将控制一个 LED 灯条(为了简化布线,我们将使用 Rainbow HAT 来代替)和一个风扇。为了简化程序化控制高压设备(如电风扇),我们将使用一个继电器模块:

继电器是电源电压的电动开关。它可以打开或关闭,让电流通过或不通过。控制继电器与控制基本 GPIO 设备(如LED)一样简单:

// Turn on the fan
relay.value = true

// Turn off the fan
relay.value = false

我们只需要调用 setValue(boolean on) 方法来打开或关闭风扇。 非常简单!该继电器模块可以控制 2 个独立的设备。 它有 4 个不同的引脚,需要连接到 Android Things 的开发板:

  • GND:接地
  • VCC:接到 5V
  • IN1:连接到 GPIO(例如 Raspberry Pi 3 上的 BCM18)。这是第一个继电器的输入
  • IN2:第二个继电器的输入。我们不需要这个,因为我们只使用 1 个继电器。

现在,我们需要将风扇插入继电器。

警告:我们要在这里操作高压设备。小心。有疑问,不要这样做。继电器通电时切勿触摸带电部件。否则可能会导致触电。操作电线时,确保电源已关闭。

我们将电源延长线连接到模块,而不是将风扇直接连接到继电器模块,并将风扇连接到延长线。这样,如果天气变得太冷,我们将风扇插上,几秒钟后用加热器替换。

首先,打开延长线,只切断相线( EU 的棕色线)。

本模块中的每个继电器有 3 个可能的连接:

  • COM:通用引脚
  • NO:常开
  • NC:通常关闭

必须将相线的一端连接到 COM,另一端连接到 NO 或 NC。

我们想偶尔打开风扇,所以基本上我们总是想要一个开路(风扇关闭)。当我们将继电器的 GPIO 值设置为真时,它关闭电路并激活风扇。 因此,我们将棕色电缆的另一端连接到 NO(常开),并用绝缘胶带覆盖所有东西。

物理设备完成。现在让我们写一些代码。

使用 Firebase

我们将使用 Firebase 数据库,并使用以下模型:

{
  "fan" : {
    "on" : false
  },
  "lights" : {
    "on" : true,
    "spectrumRGB" : 16510692
  }
}

每当价值发生变化时,Android Things 应用都会监听 Firebase的变化。

spectrumRGB 以整数形式保存颜色十六进制值(RGB中的LED颜色)。

Android Things 应用

活动时,Android 应用程序将会侦听 Firebase 更改。

我们将创建 2 个 LiveData 对象(一个用于风扇,另一个用于灯光),当 Firebase 发生更改时,我们将更新 LiveData 值。

class FanLiveData(val firebase: DatabaseReference) : LiveData<fanstate>() {

    private val valueEventListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            val isOn = snapshot.child("on").getValue(Boolean::class.java)
            value = FanState(isOn)
        }

        override fun onCancelled(error: DatabaseError) { /*handle*/}
    }

    override fun onActive() {
        firebase.child("fan").addValueEventListener(valueEventListener)
    }

    override fun onInactive() {
        firebase.child("fan").removeEventListener(valueEventListener)
    }
}

MainActivity 里,我们观察 2 个 LiveData 实例,并且当一个 livedata 值更改时,调用 MainBoardComponents 对象的方法:

fanLiveData.observe(this, Observer { fanState -&gt;
    boardComponents.setFanOn(fanState?.isOn ?: false)
})

lightsLiveData.observe(this, Observer { lights -&gt; lights?.let {
    boardComponents.setLights(it)
}})

MainBoardComponents 类将操纵物理组件,例如风扇和 LED 灯条:

class MainBoardComponents : LifecycleObserver {

    private lateinit var relay: Gpio
    private lateinit var ledstrip: Apa102

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() {
        relay = PeripheralManagerService().openGpio("BCM18")
        relay.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW)
        relay.setActiveType(Gpio.ACTIVE_LOW)

        ledstrip = RainbowHat.openLedStrip()
        ledstrip.brightness = 1
    }

    fun setFanOn(on: Boolean) {
        relay.value = on
    }

    fun setLights(lights: Lights) {
        val color = if (lights.isOn) lights.spectrumRGB else 0
        ledstrip.write(IntArray(RainbowHat.LEDSTRIP_LENGTH, { color }))
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        ledstrip.close()
        relay.close()
    }
}

这就是我们所需要的。正如你所看到的,我们不需要编写太多的代码来使所有的东西工作。

现在,我们将把这个自制的物联网设备集成到 Google 智能助理。基本上,当用户想要激活风扇时,他会与 Google 助手对话; 后者将修改一些 Firebase 数据。由于我们的物联网设备正在监听数据库更改,因此当 fan.on 值从 false 更改为 true 时,风扇将自动打开。

Actions on Google,和帐户关联

为了让 Google Home 了解我们的智能风扇,我们首先需要在 Actions 控制台上创建一个 “Actions on Google” 项目。 在这里,该项目将被命名为 “Smart home android things”:

一旦项目创建,并可供测试后,我们就可以启动 Google Home Android 应用,导航到 Google Home 设置,然后选择 “添加设备”(Add device),将您的物联网设备关联到 Google 智能助理。

此时,您应该能够在正式支持的设备中看到您的测试设备。一旦您选择了一个设备,用户必须进行身份验证,所以 Google Home 应用程序将启动一个 WebView 到您的自定义身份验证服务器。 一旦通过身份验证,auth 服务器将为 Google 提供一个有效的 OAuth2 记账凭证。

之后,当您想要打开风扇时,Google 会使用此令牌(token)来呼叫您的端点,以便您的服务知道应激活哪个风扇。

而且,正如您现在所猜测的,我们将需要一个 OAuth2 服务器。

OAuth2 服务器

如果像我一样,只处于原型阶段(例如,您开始创建智能设备,或者只想测试 Google 智能家居),那么您可能还没有生产 OAuth2 服务器。

为了测试目的,您可以使用假(模拟)OAuth2 服务器,而不是花一些时间来实现它。官方的 Smart Home sample 包含一个假的 OAuth2 服务器,您可以使用该需求。随意使用它,或者使用我的 simplified fork 代替(相同的东西,但 fork 的代码更容易阅读)。

如果您正在使用简化的分支,则只需运行 npm install start 即可在本地部署假的 OAuth2服务器。 然后,使用ngrok(ngrok http 3000``)将本地服务器公开到 Internet 并接收暴露的端点。

在 Google 控制台的操作中,使用您的 OAuth2 服务器网址指定帐户关联信息(智能家庭操作必选)。

Home 操作结点

当你要求助手 “打开风扇” 时,它将使用 bearer Token 并调用特定的端点。 要指定 Google 智能助理应调用哪个端点,您需要使用您的端点 URL 创建一个类似于以下内容的 action.json

{
  "actions": [{
    "name": "actions.devices",
    "deviceControl": {
    },
    "fulfillment": {
      "conversationName": "automation"
    }
  }],
  "conversations": {
    "automation" :
    {
      "name": "automation",
      "url": "https://example.com/ha"
    }
  }
}

然后,使用以下命令上传此操作文件:

gactions update --action_package action.json --project <google_cloud_project_id>

在这里,Google 智能助理将调用以下服务端点:https://example.com/ha

此 Web 服务将接收来自 Google 的参数(意图),并根据这些参数查询/修改 Firebase 数据库。

处理 Google 智能助理的意图

Google 智能助理会以三种不同的意图呼叫您的服务:

  • action.devices.SYNC:请求用户已连接并可供使用的设备列表 早些时候,当我们使用 Google Home 应用程序将我们的物联网设备关联起来时,它仅仅是因为发送了 SYNC 意图,而自动检测到一个风扇和一些灯光。
  • action.devices.QUERY:查询设备的当前状态(所以助手可以回答 “我的风扇在吗?”这样的问题)
  • action.devices.EXECUTE:请求在智能家居设备上执行命令(“开启风扇”)

您可以使用您想用的 Web 服务的技术栈,来创建服务处理这些意图。我决定使用 Google Cloud Function(GCF),因为这是发布由 HTTP 调用触发的代码的简单方法。 (注意:我也可以使用 “用于 Firebase 的云端函数”,稍微简化了 Firebase 的初始化,但是由于我只需要 HTTP 触发功能,所以 GCF 很好)。

以下是一个简化的 Google 云端函数,用于处理 Google 智能助理发送的意向:

exports.ha = function(req, res) {
    let authToken = req.headers.authorization ? req.headers.authorization.split(' ')[1] : null;
    let intent = req.body.inputs[0].intent;

    switch (intent) {
        case "action.devices.SYNC":
            sync(req, res);
            break;
        case "action.devices.QUERY":
            query(req, res);
            break;
        case "action.devices.EXECUTE":
            execute(req, res);
            break;
    }
}

现在,让我们来看看每个意图的实现。

SYNC

action.devices.SYNC 意图被调用时,我们需要返回设备的类型和特性。

一些设备类型的例子:

  • action.devices.types.LIGHT:一个灯泡
  • action.devices.types.OUTLET:任何插座设备
  • action.devices.types.SWITCH:一个开关设备
  • action.devices.types.THERMOSTAT:恒温器

一些特质的例子:

  • action.devices.traits.OnOff:基本的开关功能。 这对我们的风扇和我们的灯都很好
  • action.devices.traits.Brightness:如果你想让助手控制你的设备的亮度,这个功能很有用
  • action.devices.traits.ColorSpectrum:采用 RGB 颜色范围的 “全” 彩色灯泡。
  • action.devices.traits.ColorTemperature:采用 Kelvin 颜色点的“温暖”灯泡。
  • action.devices.traits.TemperatureSetting:用于恒温器来处理温度点和模式。

我们可以控制 2 个不同的设备:

  • 一个风扇(ID=“1”),这是一个 SWITCH 设备的 OnOff 性状。
  • 一些灯(ID=“2”),同时具有 OnOffColorSpectrum 特性的 LIGHT 设备。

下面是完整的 sync() 实现,返回一个 JSON 列表设备和特征:

function sync(req, res) {
    let deviceProps = {
        requestId: req.body.requestId,
        payload: {
            devices: [{
                id: "1",
                type: "action.devices.types.SWITCH",
                traits: [
                    "action.devices.traits.OnOff"
                ],
                name: {
                    name: "fan"
                },
                willReportState: true
            }, {
                id: "2",
                type: "action.devices.types.LIGHT",
                traits: [
                    "action.devices.traits.OnOff",
                    "action.devices.traits.ColorSpectrum"
                ],
                name: {
                    name: "lights"
                },
                willReportState: true
            }]
        }
    };
    res.status(200).json(deviceProps);
}

QUERY

要回答诸如 “我的风扇在吗?” 之类的问题,Google 智能助理将使用 action.devices.QUERY 意图调用我们的终端。在这里,我们将查询我们的 Firebase 数据,并返回一个有效的 json。

这是完整的实现:

function query(req, res) {
    getDevicesDataFromFirebase(devices =&gt; {
        let deviceStates = {
            requestId: req.body.requestId,
            payload: {
                devices: {
                    "1": {
                        on: devices.fan.on,
                        online: true
                    },
                    "2": {
                        on: devices.lights.on,
                        online: true,
                        color: {
                            spectrumRGB: devices.lights.spectrumRGB
                        }
                    }
                }
            }
        };
        res.status(200).json(deviceStates);
    });
}

function getDevicesDataFromFirebase(action) {
    admin.database().ref().once("value", snapshot =&gt; {
        let devices = snapshot.val();
        action(devices);
    });
}

EXECUTE

最后,当用户要求 Google 智能助理在设备上执行操作时,会调用 action.devices.EXECUTE 意图。

这是我们可以修改 Firebase 数据的地方(请记住,当数据被修改时,物联网设备将立即触发该操作)。 这是一个简化的实现:

function execute(req, res) {
    getDevicesDataFromFirebase(devices =&gt; {
        let reqCommand = req.body.inputs[0].payload.commands[0];
        let command = reqCommand.execution[0].command;
        let params = reqCommand.execution[0].params;
        let deviceId = reqCommand.devices[0].id;

        if (command === "action.devices.commands.OnOff") {
            if (deviceId === "1") {
                devices.fan.on = params.on;
            } else if (deviceId === "2") {
                devices.lights.on = params.on;
            }
        } else if (command] === "action.devices.commands.ColorAbsolute") {
            if (deviceId === "2") {
                devices.lights.spectrumRGB = params.color.spectrumRGB;
            }
        }

        admin.database().ref().set(data);
        sendResponse(req, res);
    });
}

您没有义务使用 Firebase。 如果您更喜欢使用 PubSub 或任何其他服务来通知您的物联网设备,请随意使用。

测试你的实现

如果您想在发布之前测试您的实现,可以使用一些工具,例如 Postman,或直接在官方 actionssdk-smart-home-nodejs 中使用 mock-assistant-platform 项目示例:

node platform.js [sync | query | exec]

该项目以有效的格式向您的端点发送请求。

如果所有内容都经过测试并确定,请转至 Google 控制台上的操作以将您的项目发布为测试项目。一旦作为测试项目发布,您可以使用 Google Home 并在真实条件下开始测试。如果您拥有 Android Wear 2 设备,请尝试使用手表中的 Google 智能助理来控制您的家庭设备,您会发现这也适用。

关于安全性的说明

本文演示了使用智能家居和 Google 智能助理可以快速实现的功能。 您可能会注意到,按照我在此处所做的方式(监听数据库更改)使用 Firebase 是一个糟糕的主意,因为 Android Things 不支持 Firebase 身份验证(即使通过 JWT 进行的 Firebase 身份验证也不受支持),这意味着我们被迫使用公开(public)规则(至少在现在是这样的)。这是不能用于生产环境的。

确保此项目安全的一种方法是,首先启用基于用户的安全性 Firebase 规则。然后,将 IoT 身份验证委派给移动伴侣应用(例如,将身份验证令牌发送回物联网设备),最后在收到执行意向时使用不同的技术(例如FCM)从 Web 服务通知。

这只是一个例子。 你有很多方法来保证安全。像往常一样,将事情花费更长的时间才能使事情变得安全,但是如果你正在推动生产,那么这将会为你节省麻烦和资金。

结论

使用 Android Things,Firebase 和云端功能帮助我,很快地将集成到 Google 智能助理的智能家居设备原型化。

借助智能家居,我们只需提供一个轻量级云服务,而 Google 则提供语言理解、家庭图形、各种设备的详细处理、国际化、上下文、错误管理。所有这些都是由 Google 处理的。

本文不包括您必须实施的每一步,但希望您能更好地理解 Google 智能助理的智能家居操作。 同样,请参阅官方文档,以及 I/O 2017 期间的演示文稿。

在 GitHub上 提供完整的项目: github.com/Nilhcem/smarthome-androidthings

原文链接:http://nilhcem.com/android-things/google-assistant-smart-home