65.9K
CodeProject 正在变化。 阅读更多。
Home

Zukuno | 物联网个人助理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (4投票s)

2016年8月8日

CPOL

19分钟阅读

viewsIcon

20603

downloadIcon

478

学习如何将树莓派设置为 WiFi 路由器,使用 GTFS 跟踪公共交通,安装和使用 Django,并将 CMUsphinx、Webview 和微软的语音 API 结合到一个 Android 应用中。我们还将控制 Sonos 扬声器,构建一个安全摄像头,并使用一些 API(包括 CodeProject API)。

观看一个简短的语音控制声音演示

[图 1.] 我的 GUI 在原型制作时的 GIF 屏幕录像 ^.^

目录

引言

大家好,欢迎阅读我的最新 CodeProject 文章!

上次我在 CodeProject 写文章(是去年的十月),我还在读高中。从那时起,我进入大学学习数学和 IT,并且一直忙于学业,没时间写文章 :-(。然而,几周前我完成了大学第一学期的学习,我知道在第二学期开始前有几周的休息时间,所以我考虑再写一篇 CodeProject 文章。然后我查看了 Code Project,发现有一个物联网竞赛!受到 Google Home 和 Amazon Alexa 发布的影响,我想尝试一下语音控制的家庭自动化——这个项目就这样诞生了 :cool

在这篇文章中,我将向您展示如何构建您自己的个人助理。我们将首先配置一个树莓派作为 WiFi 路由器。之后,我们将安装 Apache 和 Django,原型化我们的 UI,并围绕几个 API 构建我们自己的包装器。然后我们将构建一个消耗我们构建的 API 的 Android 应用,并通过结合 CMUsphinx 和微软的 Bing Voice API 为我们的应用添加语音识别和合成。然后,我将向您展示如何使用另一个树莓派和 USB 摄像头创建实时安全馈送。最后,我们将通过 Python 的 SoCo 库演示如何控制您的 SONOS 音响系统,并探讨该项目的未来扩展可能性。

为什么要进行家庭语音自动化?我认为语音自动化是一个很酷的研究领域,因为它可以在多种方式中应用。例如,我创建的个人助理(Zukuno)可以轻松优化,通过语音为残疾人(如四肢瘫痪、失明等)提供更大的环境控制能力。它还可以扩展到工业环境中,自动化设备和监控物联网服务。语音技术的内在灵活性使得选择本文的类别有些困难。

总之,废话少说。开始编码吧!

我们需要什么

要构建我们的个人助理,我们需要

  • 两台树莓派(一台用于主 WiFi 路由器,一台用于控制安全摄像头)
  • 两张 8GB 或更大的 micro SD 卡
  • 一个 USB 扬声器
  • 一个 USB 摄像头
  • 电源适配器
  • 一个可用的以太网端口(用于主 WiFi 路由器)
  • 一台 Android 智能手机或平板电脑(旧的也可以。我们用它来显示我们的 GUI 并处理麦克风输入)
  • 一根 HDMI 线缆和一个支持 HDMI 的显示器(非必需,但在对 rPi 进行故障排除时非常有用)
  • 如果您使用的不是最新型号的树莓派(型号 3),那么您将需要 WiFi-USB 适配器来完成此项目。

有用的

  • 中等水平的编程经验
  • 大量的耐心
  • 几个周末的空闲时间

鸟瞰图

[图 2.] 我们项目的鸟瞰图。

我们整个系统的核心组件是单个树莓派 3。树莓派 3 通过以太网端口连接到我们的主家庭网络,它既是 WiFi 路由器也是 Apache/Django 服务器,并提供了一个中央 API 供 Zukuno 优化的物联网设备访问。通过利用放置在 Zukuno 安装目标空间的战略位置的 Android 平板电脑,Zukuno 能够收听命令并显示视觉输出。连接到树莓派的 USB 扬声器为 Zukuno 提供了集中的音频输出,尽管平板电脑/设备可以配置为在需要时提供本地化输出。

使用壁挂式平板电脑的好处是它们提供了足够的麦克风质量、出色的视听输出,以及一个用于执行语音识别等 CPU 密集型任务的本地环境。如果语音识别完全在 Pi 上执行,随着系统增加的房间越多,Pi 上的 CPU 负载将急剧增加,最终使整个系统无法运行。

开始使用树莓派

设置

树莓派于 2009 年 5 月首次问世,现已推出第三个官方版本。直到今年 5 月,我从未买过树莓派,主要是因为它们似乎有限。然而,当树莓派 3 推出内置 WiFi 时,我终于有动力购买一台。我很庆幸最终买了一台,否则我就写不了这篇文章了 :-)

[图 3.] 我的树莓派 3,带外壳,附带尺子作为比例尺

我强烈建议为您的树莓派购买一个外壳,因为它们很小,很容易被大物体损坏。

下载和刻录 Raspbian 镜像

这很简单。事实上,这个过程的简单性是树莓派(以及其他物联网设备)的主要卖点之一:如果您搞砸了您的安装,很容易移除 SD 卡,重新刻录镜像,然后从头开始用一个全新的操作系统启动您的 Pi。

前往 官方 Raspbian 下载页面 下载 Raspbian。

下载 Raspbian 镜像后,将一张 8GB 或更大的 MicroSD 卡插入您的机器。如有必要,请使用适配器。然后,解压镜像文件,并使用 Win32DiskImager 将镜像刻录到您的 SD 卡上。

首次启动

要首次启动您的 Pi,您应该确保您有一个能够输出至少 5A 的 micro-USB 充电器。我个人发现 Pi 通常在较低功率下也能正常运行,例如手机充电器产生的功率,尽管在低功率电源上运行 CPU 密集型任务时有时会出现电压骤降。

首先将您刻录好的 SD 卡插入树莓派下方的卡槽。或者,通过将有源以太网线插入以太网端口来连接您的 Pi 到互联网。如果您有支持 HDMI 的显示器,请准备一根 HDMI 线缆并将您的 Pi 连接到显示器。完成所有这些后,将 micro-USB 充电器插入 Pi 上的电源连接器。您的树莓派将自动启动,屏幕上会显示一串文本输出。

[图 4.] 我的树莓派 3 启动。

更改密码

您的树莓派的默认登录名是“pi”(用户名,不含引号),密码是“raspberry”(密码,不含引号)。

我强烈建议在继续操作之前将您的密码更改为易于记忆且安全的值。很多人会跳过这一步,认为“我马上会做”,但我知道有几个人吃过苦头才明白这一步的重要性。请务必完成。

要更改密码,请输入此命令

$ sudo passwd

系统会提示您输入当前密码(raspberry),之后您就可以选择一个新密码。

完成后,尝试输入

$ startx

来玩 Raspbian 自带的 GUI。您需要将 USB 鼠标插入 Pi 才能使用 GUI。花点时间熟悉操作系统。

[图 5.] Raspbian GUI

创建 WiFi 热点

在不编写任何代码的情况下,您可以做的最酷的事情之一就是将其配置为充当 WiFi 路由器。这是开启您物联网之旅的一个绝佳方式,也是构建我们个人助理的关键一步。

安装 hostapd 和 dnsmasq

网上有很多关于如何将树莓派设置为 WiFi 服务器的教程。由于撰写时间不同,其中许多教程互相冲突,所以我决定分享我在树莓派 3 上运行 Raspbian Jesse(使用内置 WiFi 硬件)时有效的方法。

首先打开终端并安装这两个软件包:hostapd 和 dnsmasq

$ sudo apt-get update
$ sudo apt-get install hostapd dnsmasq

配置

打开 /etc/network/interfaces 进行编辑

$ sudo nano /etc/network/interfaces

进行编辑,使其看起来像这样

[图 6.] 编辑 /etc/network/interfaces

之后,在 /etc/hostapd/hostapd.conf 创建一个新的配置文件

$ sudo nano /etc/hostapd/hostapd.conf

输入此内容

interface=wlan0
driver=nl80211
ssid=Pi3-AP #name of your new wifi hotspot
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_passphrase= {YOUR DESIRED WIFI PASSWORD GOES HERE}
rsn_pairwise=CCMP

确保您选择一个 WiFi 密码,并将 {YOUR DESIRED WIFI PASSWORD GOES HERE} 替换为正确的值。我知道以明文存储密码是糟糕的安全实践,但遗憾的是 hostapd 就是这样工作的。Hostapd 是一个开源项目,如果您对此感到担忧,可以随时 fork 他们的代码并实现安全的密码哈希。

现在按顺序运行这些命令

$ sudo service dhcpcd restart
$ sudo ifdown wlan0; sudo ifup wlan0
$ sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf

此时,您应该能够使用您的智能手机/笔记本电脑预览您的 Wifi 网络。但是,您将无法连接或访问互联网,因为我们尚未配置 dnsmasq。

$ sudo nano /etc/dnsmasq.conf

然后将该文件的内容更改为

interface=wlan0
listen-address=172.24.1.1
bind-interfaces
server=8.8.8.8 # Use Google's DNS servers.
domain-needed
bogus-priv
dhcp-range=172.24.1.50,172.24.1.150,24h

现在,要启用 IPv4 转发,请打开 /etc/sysctl.conf

$ sudo nano /etc/sysctl.conf

通过删除行首的 # 来取消注释行 net.ipv4.ip_forward=1。按 Ctrl-X,然后按 y,最后按 Enter 保存。

现在输入以下命令

$ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE  
$ sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT  
$ sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
$ sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
$ sudo nano /etc/rc.local

exit 0 行的上方,添加以下文本行

iptables-restore < /etc/iptables.ipv4.nat

如果您在上述步骤中遇到任何问题,请尝试搜索,或参考我写这篇文章时使用的文章

  • http://raspberrypihq.com/how-to-turn-a-raspberry-pi-into-a-wifi-router/
  • https://frillip.com/using-your-raspberry-pi-3-as-a-wifi-access-point-with-hostapd/

检查连接

使用以下命令重启您的 Pi

$ sudo reboot

如果一切顺利,当您的 Pi 加载完成后,您应该能够将其他设备连接到您新的 WiFi 网络!

[图 7.] iPhone 连接到我的 Pi3-AP 网络

通过 PuTTY 连接

现在您已经设置好了网络,请将您的 PC 连接到它。如果您还没有 PuTTY,请从官方网站 下载,然后运行它。

[图 8.] PuTTY

输入您的树莓派的详细信息(IP 地址:172.24.1.1,端口:22,就是这些!),然后点击“Open”。

[图 9.] 使用 PuTTY SSH 连接到我们新的树莓派 WiFi 路由器

现在我们可以从桌面远程访问终端了,我们不再需要使用 Raspbian GUI。如果需要,您可以断开 Pi 与显示器的连接,并将 Pi 安全地放置在附近的一个角落,以便它可以接入电源和以太网。初学者请注意,他们不使用 WiFi 密码通过 PuTTY 访问他们的 Pi,而是使用本文前面通过 passwd 设置的 Raspbian 账户密码。

设置我们的工作流程

Git

Git 是一个非常流行的文件管理和版本控制系统,通常与独立服务 GitHub 相关联,尽管存在许多其他基于 Git 的服务,并且许多人运行自己的 Git 服务器。

在本文的其余部分,我们将使用 Git 将我们的代码推送到我们的树莓派并进行部署。

Visual Studio Code

Visual Studio Code 是一个出色的现代文本编辑器,我强烈推荐您在本文中使用它。您也可以选择其他文本编辑器,但我是坚定的相信者,应该学会适应各种文本编辑器,以便能够以开放的心态为每项任务选择最佳工具。例如,虽然我在这篇文章中推荐 VS Code,但我不会用 VS Code 来编辑一个 2GB 的 .csv 文件。像 Vim、Nano,或者 Windows 上的 Notepad++ 这样的编辑器会更适合这个任务。

创建和使用存储库

要在您的树莓派上创建一个新的 Git 存储库,请使用 PuTTY SSH 连接到您的 Pi,并创建一个名为“GitTest”的新目录。

$ mkdir GitTest

然后进入该目录。

$ cd GitTest

现在,初始化一个新的 git 仓库

$ git init
$ git add .
$ git commit -m "First Commit"

要推送到远程存储库,例如 GitHub 上的存储库,请执行以下操作

$ git remote add origin URL-of-your-remote-repository
$ git remote -v
$ git push -u origin master

在进行了本地更改后,您可以通过以下方式轻松地将它们推送到存储库

$ git push

同样,可以将远程更改拉取到本地存储库

$ git pull

构建我们的后端

Zukuno 分为两个部分:一个通过 Apache 动态提供给参与设备的用户界面,以及一个消耗该 UI 的 Django 驱动的 API,它使一切得以运行。在本节中,我们将重点构建 Zukuno 的后端 API。

安装和配置 Apache

首先安装 Apache 软件包

$ sudo apt-get install update
$ sudo apt-get install apache

安装完成后,启动 Apache 服务

$ sudo service apache start

现在,您可以通过在您的电脑上打开 Chrome 并导航到 172.24.1.1(您的树莓派的 IP 地址)来测试您的服务器。您应该会看到 Apache 的欢迎页面。我们的 WiFi 路由器现在也是一个服务器了 :cool

通过 Apache 服务器提供的文件位于您树莓派的 /var/www/ 目录下。导航到该位置并创建并推送到 GitHub 的一个 Git 存储库,然后将其克隆到您的桌面。然后,每当您对桌面存储库进行更改时,您都可以通过 git push 将它们推送到 GitHub,并通过 git pull 将它们加载到您的树莓派。

安装和配置 Django

我们正在慢慢进入这个构建更高级的部分。与 Apache 一起安装 Django 有点棘手。我强烈建议 遵循官方文档 进行操作,也许可以设置一个虚拟环境来工作等等 :-)

您可以使用以下命令测试您的 Django 安装

$ python -m django --version

假设您已经搞定了 Django,让我们为我们的项目创建一个新文件夹

$ cd ../
$ mkdir django
$ cd django
$ mkdir zukuno
$ cd zukuno

现在让我们创建一个新的 Django 项目

$ django-admin startproject zukuno

做完这些之后,让我们确保我们拥有刚刚创建的所有文件

$ sudo chown -R $USER:$USER .

我们现在将拥有一个具有以下结构的目录

zukuno/
    manage.py
    zukuno/
        __init__.py
        settings.py
        urls.py
        wsgi.py

让我们启动服务器。导航到包含 manage.py 的文件夹,然后运行以下命令

sudo python manage.py runserver 0.0.0.0:8000

末尾的 0.0.0.0:8000 告诉 Django 接受外部连接,使用端口 8000。

[图 10.] 在我们的树莓派上运行 Django

现在,如果您在连接到树莓派网络的设备上加载 http://172.24.1.1:8000/,您将看到类似图 11 的内容(来自我们稍后将讨论的 Android 应用的一个非常早期的版本)。

[图 11.] 默认的 Django 欢迎页面,运行在我们稍后将介绍的 Android 应用的一个早期版本中

要优雅地关闭服务器,请在终端活动时按下 Ctrl-C。不幸的是,我发现有时 Django 无法完全退出,阻塞了 8000 端口,导致服务器无法重新启动。如果发生这种情况,请使用此命令强制清除端口,以便您可以重新启动服务器

$ sudo fuser -k 8000/tcp

使用 GTFS 源

GTFS 简介

根据 Google 的官方文档,GTFS Realtime(General Transit Feed Specification)是“一个允许公共交通机构向应用程序开发者提供关于其车队实时更新的馈送规范”。

GTFS 作为通用协议,还需要 几个静态文件 作为参考,以便在实时数据不可用时使用。

在构建此项目时,我参考了以下来源的 GTFS 数据

  • https://translink.com.au/about-translink/open-data
  • https://gtfsrt.api.translink.com.au/

安装 Google 的 Python 包装器

[图 12.] 在 Windows 上安装 gtfs-realtime-bindings。

幸运的是,我们不需要花费大量时间来构建一个馈送解析器来处理这个协议。相反,我们可以使用 Google 预先构建的 Python 包

安装它只需要一行

$ pip install --upgrade gtfs-realtime-bindings

试用

在您的树莓派主目录下创建一个名为 gtfs-test.py 的新文件

$ nano gtfs-test.py

[图 13.] 使用下面的代码以及 GTFS 库

让我们尝试一些简单的。将以下代码添加到文件中

from google.transit import gtfs_realtime_pb2
import urllib

feed = gtfs_realtime_pb2.FeedMessage()
response = urllib.urlopen('https://gtfsrt.api.translink.com.au/Feed/SEQ')
feed.ParseFromString(response.read())
vehicles = []
for entity in feed.entity:
    if entity.trip_update.vehicle.id not in vehicles:
        vehicles.append(entity.trip_update.vehicle.id)
        print entity.trip_update.vehicle.id

print "There are " + str(len(vehicles)) + " buses on the road"

保存并退出。然后使用以下命令运行该文件

$ python gtfs-test.py

生成的输出的最后一行将类似于

There are 974 buses on the road

我们刚刚收集了唯一的车辆标识符,并使用它们的总数计算出当前有多少辆公交车正在运行 :cool

我们的代码

要开始,请停止正在运行的 Django 进程,然后导航到您的 Django 项目中包含 manage.py 的文件夹。到达那里后,运行以下命令

$ python manage.py startapp gtfs

这将创建一个新的“应用程序”gtfs,它就像 Django 项目的一个子模块。在 nano 中打开 gtfs/views.py 并将其更改为以下内容

from django.shortcuts import render
from google.transit import gtfs_realtime_pb2
import urllib

# Create your views here.

from django.http import HttpResponse, JsonResponse

def index(request):
    feed = gtfs_realtime_pb2.FeedMessage()
    response = urllib.urlopen('https://gtfsrt.api.translink.com.au/Feed/SEQ')
    feed.ParseFromString(response.read())
    trip_updates = []
    for entity in feed.entity:
        if entity.HasField('trip_update'):
            for stop_update in entity.trip_update.stop_time_update:
                if stop_update.stop_id == '<the stop you wish to use>':
                    delay = stop_update.arrival.delay
                    append = " ahead"
                    if delay < 0:
                        delay = delay * -1;
                        append = " late"
                    if delay < 60:
                        delay = "30 seconds" + append
                    else:
                        delay = str(delay / 60) + " minutes" + append
                    
                    trip_update = {
                        'route_id': entity.trip_update.trip.route_id,
                        'time': stop_update.arrival.time,
                        'delay': delay,
                        'vehicle_id': entity.trip_update.vehicle.id
                    }
                    trip_updates.append(trip_update)
    
    trip_updates.sort()
    
    #json_output = jsonpickle.encode(trip_updates, unpicklable=False)
    return JsonResponse(trip_updates, safe=False)

要查找您家外面的站点,请参考 GTFS 标准中提供的静态文件。您应该能够查找您的街道名称并找到匹配的站点。

上面的代码基本上是一个 JSON 简化器。它获取实时馈送的巨大 JSON 响应,提取我们想要的一小部分信息,并以不同的 JSON 结构返回。

未来改进

我上面写的代码示例只是对这里可能性的一个非常简单的演示。例如,我可以跟踪这些数据几个月,然后应用机器学习算法来尝试理解何时发生运输延误,为什么会发生,以及如何避免它们。然而,我用于此项目的时间有限,所以目前我必须保持现状。

使用微软的 TTS 引擎

为什么选择这个引擎?

我选择使用微软的语音 API 是因为

  • 它每月有合理的免费配额
  • 它足够准确
  • 它比我尝试过的其他替代方案更容易使用

获取 API 访问密钥

要获取用于编译我的示例代码或构建您自己的应用程序的 API 密钥集,请访问 https://www.microsoft.com/cognitive-services/en-us/speech-api

试用

首先,我们定义一些关键参数

clientId = "<insert>"
clientSecret = "<insert>"
ttsHost = "https://speech.platform.bing.com"
params = urllib.urlencode({'grant_type': 'client_credentials', 'client_id': clientId, 'client_secret': clientSecret, 'scope': ttsHost})
headers = {"Content-type": "application/x-www-form-urlencoded"}            
AccessTokenHost = "oxford-speech.cloudapp.net"
path = "/token/issueToken"

之后,我们联系微软服务器请求访问令牌

conn = httplib.HTTPSConnection(AccessTokenHost)
conn.request("POST", path, params, headers)
response = conn.getresponse()
data = response.read()
conn.close()
accesstoken = data.decode("UTF-8")
ddata=json.loads(accesstoken)
access_token = ddata['access_token']

现在我们有了访问令牌,我们可以用我们的文本来查询他们的服务器进行朗读

body = "<speak version='1.0' xml:lang='en-us'><voice xml:lang='en-us' xml:gender='Female' name='Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)'>" + text_to_dictate + "</voice></speak>"
headers = {"Content-type": "application/ssml+xml", 
            "X-Microsoft-OutputFormat": "riff-16khz-16bit-mono-pcm", 
            "Authorization": "Bearer " + access_token, 
            "X-Search-AppId": "99aab5c6fd784c0dac57d1fe01059c3c", #"07D3234E49CE426DAA29772419F436CA", 
            "X-Search-ClientID": "099a0afb96044b298daeb6ce9cc131ce", #"1ECFAE91408841A480F00935DC390960", 
            "User-Agent": "TTSForPython"}
conn = httplib.HTTPSConnection("speech.platform.bing.com")
conn.request("POST", "/synthesize", body, headers)
response = conn.getresponse()
data = response.read()
conn.close()

最后,我们将录音保存到本地磁盘

file = open("/home/pi/response.wav", "w")
file.write(data)
file.close()

我们的代码

我在我的应用程序中使用的代码与上面的代码几乎相同,只是我将其包装在一个函数中并在其周围创建了一个类,以便我的 Django 视图应用程序可以在需要时调用它

from voice import tts

response = "I've paused the music."
tts.getDictation(response); # Use Microsoft Speech API to dictate the input
player = subprocess.Popen(["omxplayer", "-o", "local", "/home/pi/response.wav"], stdin=subprocess.PIPE

构建我们的前端

我的前端方法

我决定将 Zukuno 的前端构建为一个 Web 应用。这带来了一些积极和消极的影响。这种方法的积极影响之一是更新 GUI 非常容易——我只需要更新树莓派上的文件,更改就会自动流向访问这些文件的每个设备。不幸的是,这种方法的消极影响是我在设计和响应性方面受到严重限制,这主要是由于 Android 的 WebView 组件性能极其低下。

使用 Android Studio

如果您想构建现代 Android 应用,我推荐使用 Android Studio。自 2014 年 12 月第一个稳定版发布以来,它一直在稳步改进——如今它是开发原生 Android 应用的最佳 Android IDE。
 

[图 14.] Android Studio

使用 WebView

WebView 已损坏

正如我之前提到的,我们的应用程序使用 Android 的 WebView 组件来显示 Zukuno UI。不幸的是,WebView 在很大程度上是损坏且未维护的,因此有时会遇到性能问题并表现异常。在接下来的几个部分中,我将解释我提出的一些解决方案。

启用触摸事件

在使用 WebView 时,我遇到了一种奇怪的行为,即几乎不可能从 Web 应用中的 JavaScript 代码接收 onClick 事件,甚至 onTouch 事件。我仍然没有找到一个干净的解决方法;尽管我确实找到了以下 hackish 解决方案

main_view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getPointerCount() > 1) {
            return true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                m_downX = event.getX();
                // send the click event
                main_view.loadUrl("javascript:$(document.elementFromPoint(" + event.getX() + ", " + event.getY() + ")).click()");
            }
            break;

            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                // prevent horizontal scrolling
                event.setLocation(m_downX, event.getY());
            }
            break;
        }

        return false;
    }
});

修复性能

WebView 中的开箱即用 CSS 动画和变换效果非常糟糕。我发现这些更改有所帮助。动画仍然远非完美,但至少现在可以观看了。

main_view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
main_view.getSettings().setAppCacheEnabled(false);
main_view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
main_view.getSettings().setJavaScriptEnabled(true);
main_view.setVerticalScrollBarEnabled(false);
main_view.setHorizontalScrollBarEnabled(false);

通过 JavaScript 进行通信

WebView 组件的一个可取之处在于它支持 JavaScript 通信非常容易。尽管在我用过的其他平台(CefSharp、C# WebBrowser 控件等)中,JavaScript 很难正常工作,但在 Android 的 WebView 中,它就可以工作。

Java -> JavaScript

main_view.loadUrl("javascript:alert('triggered')");

JavaScript -> Java

Java

public class HelloWorld {
    Context ctx;

    HelloWorld(Context c) {
        ctx = c;
    }

    public void fooBar() {
        //do something
    }
}

//In onCreate()
main_view.addJavascriptInterface(new HelloWorld(this), "HelloWorldPortal");

然后在 JavaScript 中

HelloWorldPortal.fooBar();

在 WebView 中构建我们自己的 GUI

结构

我们的 HTML 结构如下

<html>
<head>
    <title>Alex</title>
    <link href="https://fonts.googleapis.com/css?family=Roboto+Slab:400,300,700" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="style.css" />
</head>
<body>
    <div id="mic">
        <img src="img/mic.png" />
        <div class="spinner">
            <div class="bounce1"></div>
            <div class="bounce2"></div>
            <div class="bounce3"></div>
        </div>
    </div>
    <div id="rec-text">
        <span></span>
        <div class="circle-waiter"></div>
    </div>
    <div id="response"></div>
    <div id="shade"></div>
    <!--<div id="header"></div>-->
    <div id="space">
        <div id="left-space">
            <img src="http://172.24.1.73:8081">
        </div>
        <div id="right-space"></div>
    </div>
    <div id="times">
        <h1>Upcoming Buses</h1>
        <div id="sched">
            <div class="entry">
                <span class="service">P205</span>
                <span class="time"><span>ETA</span>6:00am</span>
                <span class="data">
                    <div>
                        <span class="late">3 minutes late</span>
                        <span class="details">to City, George St.</span>
                    </div>
                </span>
            </div>
... lots of entries ...
        </div>
    </div>
    <script src="jquery.min.js"></script>
    <script src="main.js"></script>
</body>
</html>

使用我们构建的 API

播放激活/停用声音效果非常容易

$.get("http://172.24.1.1:8000/voice/?p=up-listening"); //Activated!
$.get("http://172.24.1.1:8000/voice/?p=down-finished"); //De-activated!

发送其他信息如下

$.get("http://172.24.1.1:8000/voice/?p=" + rec, function(result) {
    showResponse(result);
    setTimeout(function() {
        StopRecognizing();
    }, 2000);
});

以下代码更新实时公交信息

function getTripData() {
    is_collecting = true;
    console.log("Collecting data");
    $.getJSON("http://172.24.1.1:8000/gtfs/", function(result){
        is_collecting = false;
        console.log("Parsing data");

        $('#sched').html('');
        data = result;
        $.each(result, function(i, field){
            $('#sched').append(generateTripSchema(field.route_id, formatAMPM(field.time), field.delay));
        });
    });
}

function generateTripSchema(route_id, eta, delay) {
    var template = `
            <div class="entry">
                <span class="service">` + route_id + `</span>
                <span class="time"><span>ETA</span>` + eta + `</span>
                <span class="data">
                    <div>
                        <span class="late">` + delay + `</span>
                        <span class="details">to City, George St.</span>
                    </div>
                </span>
            </div>
        `;
    return template;
}

[扩展我们的系统] 使用 SoCo Python 库

使用 SoCo 控制我的家庭 Sonos 系统非常简单。

安装

pip install soco

用法

import soco

# Pause everything
zone_list = list(soco.discover())
for zone in zone_list:
    zone.pause()

# Play Mozart
zone_list = list(soco.discover())
for zone in zone_list:
    zone.play_uri('https://ia802300.us.archive.org/18/items/MozartCompleteWorksBrilliant170CD/Volume%201%28CD01%29%20Symphonies%20KV%2016-19-19A-22-43-45.mp3')

就是这样!我只需要在检测到相应意图时调用 voice Django 应用程序中的这些函数。

[扩展我们的系统] 设置安全摄像头

我对此项目进行的最后一个扩展是添加一个“安全摄像头”。

为了做到这一点,我使用了第二个树莓派、一个 USB 摄像头、一个带供电的 USB 集线器和一个名为 Motion 的软件包。

安装 motion 很简单

$ sudo apt-get install motion

安装完成后,打开 /etc/motion.conf

$ sudo nano /etc/motion.conf

然后找到引用本地主机连接的部分,并将所有内容设置为关闭,这样您就可以从主机 Pi 外部访问流。之后,重启 Pi,您就可以开始了。

[图 16.] 通过浏览器访问我们的安全摄像头

结论

[图 17.] 接近我们应用的当前版本

感谢您阅读到最后!

Zukuno 仍然是一个很大的项目,我在这里展示的只是它能力的骨架。在接下来的几个月里,我想逐渐将更多的 API 添加到 Zukuno 的代码库中,这样它就可以展翅高飞,充分实现其作为物联网个人助理的目标。我从迄今为止的构建中学到了很多东西,我希望这篇文章能激励人们尝试构建自己的物联网应用程序。我个人认为,像 Zukuno 这样的语音控制应用程序在帮助残疾人(如四肢瘫痪、失明等)控制他们的环境并获得更大独立性方面具有巨大的潜力。

总而言之,我希望您喜欢这篇文章 :-) 请在下面的论坛中留下您的任何评论。我渴望听到您的反馈。

历史

  • 16/8/7 首次发布
© . All rights reserved.