Snips 和 Raspberry Pi 3 构建您自己的天气语音助手

你可能听说过像 Amazon Echo 和 Google Home 这样的语音助手。也许,你也对通过声音控制你的家的想法感到好奇。然而,您的家庭也是您最亲密的空间,您在安装一个永远监听的云端连接设备之前,会进行深思熟虑地,将您的数据传输到远程服务器上...不要再担心,我们为您提供了一些特别的东西!😜

在 Snips,我们正在而对一个挑战,将人工智能添加到我们的日常生活中,就意味着放弃隐私的想法。这是为什么我们最近推出了 Snips 语音平台的信念,这是一个 Amazon Alexa 的 100% 替代品,允许任何人轻松地在连接的设备上,添加强大的语音助手,而不会影响其隐私。

在这篇文章中,我将引导您完成 3 个简单的步骤,以建立您自己的“私人设计” 的 Amazon Echo。

在一小时之内,您应该在您面前有一个功能性的语音助手,用于回应天气查询。主要步骤如下:

  1. 建立你自己的助手
  2. 在你的 Raspberry Pi 3 上设置 Snips。
  3. 处理天气要求。

步骤 0:材料准备

材料清单

我们需要的有:

  1. Raspberry Pi 3
  2. 麦克风
  3. 扬声器

如果您准备好了这个材料,我们继续前进!

步骤1:建立你自己的助手

目标:在第一步中,我们将构建一个组件,它能够从天然语言表达的天气查询中,提取明确的意图、数据和位置。

询问天气

跳转到 https://console.snips.ai/intents,然后登录。

你现在在 Snips 的控制台上。 在这里,您可以教导您的助理,了解您希望处理的任何命令。现在,我们对于与天气相关的查询感兴趣。因此,让我们创建一个助手,点击左上角的菜单,并将其命名为 WeatherAssistant。

Snips 创建 WeatherAssistant

SET UP (“设置”)选项卡下,我们将保留当前的参数:我们使用将在您的设备上运行的 Snips 的自动语音识别(ASR)解决方案,以英语为对象。

您现在可以点击 INTENTS 选项卡。

在这里,您可以提供由助理处理的意图(intents)。对于天气相关的查询,我们已经有一套准备好了的意图(intents)。我们称之为捆绑(bundle)。 向下滚动并将其添加到您的助手。

向上滚动。你可以看到 3 个意图(intents)被添加到助理:

  • SearchWeatherForecast:一般天气相关的问题
  • SearchWeatherForecastCondition:针对天气条件的问题
  • SearchWeatherForecastTemperature:针对温度问题

要完成这一步,只需训练你的模型:

重新训练助手

现在,在右侧的控制台中,测试它以进行验证。

🎯 验证步骤1:

在控制台中输入『How’s the weather today in Paris?』。这时应该检测到 SearchWeatherForecast 的意图,与相应的时间和位置相关联。

步骤2:在 Raspberry Pi 3 设置 Snips

目标:在你的 Raspberry Pi 3 上搭建并运行 Snips。我们希望它在你叫 “Hey Snips” 时做出反应,当我们大声问候天气时,我们希望它能够理解。

由于,我们已经在其他玩法里,介绍如何进行基本的 Raspberry Pi 搭建,这里仅列一下步骤:

  1. 在 SD 卡上安装 Raspbian Jessie Lite
  2. 允许 SSH
  3. 连接 Raspberry Pi 到互联网中

接下来,我们就可以进入下一步。

安装 Snips

如上所述连接到您的 Raspberry Pi 3。然后运行:

$ curl https://install.snips.ai -sSf | sh

此命令将安装 Snips 及其依赖项,以及一些方便的工具。当被问及时,请确认您愿意安装 Docker 和 python。

⚠️ 如果您获得权限被拒绝的错误,请重新登录您的 Raspberry Pi 3,并再次运行安装脚本。

设置声音的输入和输出

从您的 Raspberry Pi 3,运行以下命令:

$ aplay -l

应该触发如下的响应:

**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

从该命令的输出,我们知道音频输出链接到设备(card 0 )和设备0(device0)。这就是提到的 hw:0,0。指标在您的设置中可能会有所不同,这就是为什么我们要执行这些步骤。

同样,要知道音频输入的声卡和设备索引:

$ arecord -l

如:

**** List of CAPTURE Hardware Devices ****
card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
 Subdevices: 1/1
 Subdevice #0: subdevice #0

音频输入连接到 声卡1 和设备0,这将是提到的 hw:1,0。

有了这些信息,我们可以相应地更新 〜/.adoundrc 文件:

$ nano ~/.asoundrc

安装你的助手

现在,在验证 Snips 是搭建好、运行之前的最后一步,便是安装您在步骤1 中准备的助理。从您的计算机执行:

$ scp <path_to_assistant_zip> pi@raspberrypi.local:assistant.zip

然后从你的 Raspberry Pi 上执行:

$ snips-install-assistant assistant.zip

🎯 验证步骤2

我们现在准备好验证 Snaps 在您的 Raspberry Pi 3 上运行。在一个终端窗口中,从您的Raspberry Pi 3运行

$ snips

这将启动 Docker 容器中的所有 Snips 组件。 从另一个窗口中,仍然从您的Raspberry Pi 3,运行以下:

$ snips-watch

这将简明扼要地告诉你发生了什么。一旦应用完成所有的加载,只需要唤醒 “Hey Snips” 就能唤醒助手,它应该引起一些反应。它被称为唤醒词,它只是与你的助手沟通,然后你才能多问一些事情。

你现在可以说 “How’s the weather today in Paris?”。 您应该从日志中看到 SearchWeatherForecast 意图已被触发。

让我花一点时间解释,刚刚发生在您的设备上的事情:唤醒词检测、语音识别和自然语言理解。如果你想确保这是真的,关掉你的 WiFi,再试一次!

他们现在只剩下一步,就是告诉你的助手:如何回应这样的查询。

步骤3:处理天气请求

目标:在这里,我们只是要求我们的助手,在问及天气时做出回应。

这一步很容易。登录到 Open Weather Map。我们将依靠他们的 API,来获取有关天气的信息。一旦登录后,请转到 API 密钥选项卡,然后复制您的密钥。

现在,下载以下脚本,并替换 <weather_api_key><city_name> 参数:

# encoding: utf-8
from __future__ import unicode_literals

import datetime
import json

import paho.mqtt.client as mqtt
import requests

fromtimestamp = datetime.datetime.fromtimestamp

# MQTT client to connect to the bus
mqtt_client = mqtt.Client()
HOST = "localhost"
PORT = 9898
WEATHER_TOPICS = ['hermes/intent/SearchWeatherForecastTemperature',
                  'hermes/intent/SearchWeatherForecastCondition',
                  'hermes/intent/SearchWeatherForecast']

# WEATHER API
WEATHER_API_BASE_URL = "http://api.openweathermap.org/data/2.5"
WEATHER_API_KEY = "<your_app_id>"
DEFAULT_CITY_NAME = "<your_city>"
UNITS = "metric"



# Subscribe to the important messages
def on_connect(client, userdata, flags, rc):
    for topic in WEATHER_TOPICS:
        mqtt_client.subscribe(topic)


# Process a message as it arrives
def on_message(client, userdata, msg):
    print msg.topic

    if msg.topic not in WEATHER_TOPICS:
        return

    slots = parse_slots(msg)
    weather_forecast = get_weather_forecast(slots)


    if msg.topic == 'hermes/intent/SearchWeatherForecast':
        '''
        Complete answer: 
            - condition
            - current temperature
            - max and min temperature
            - warning about rain or snow if needed
        ''' 
        response = ("It will be mostly {0}{1} today. "
                    "Current temperature is {2} degrees. " 
                    "Max temperature will be {3}. "
                    "Minimum will be {4}.").format(
            weather_forecast["mainCondition"], 
            weather_forecast["inLocation"],
            weather_forecast["temperature"], 
            weather_forecast["temperatureMax"], 
            weather_forecast["temperatureMin"]
        )
        response = add_warning_if_needed(response, weather_forecast)

    elif msg.topic == 'hermes/intent/SearchWeatherForecastCondition':
        '''
        Condition-focused answer: 
            - condition
            - warning about rain or snow if needed
        ''' 
        response = "It will be mostly {0}{1} today.".format(
            weather_forecast["mainCondition"], 
            weather_forecast["inLocation"]
        )
        response = add_warning_if_needed(response, weather_forecast)

    elif msg.topic == 'hermes/intent/SearchWeatherForecastTemperature':
        '''
        Temperature-focused answer: 
            - current temperature
            - max and min temperature
        ''' 
        response = ("Current temperature{0} is {1} degrees. "
                    "Today, max temperature will be {2}. "
                    "Minimum will be {3}.").format(
            weather_forecast["inLocation"],
            weather_forecast["temperature"], 
            weather_forecast["temperatureMax"], 
            weather_forecast["temperatureMin"]
        )

    say(response)


def parse_slots(msg):
    '''
    We extract the slots as a dict
    '''
    data = json.loads(msg.payload)
    return {slot['slotName']: slot['rawValue'] for slot in data['slots']}


def say(text):
    '''
    Print the output to the console and to the TTS engine
    '''
    print(text)
    mqtt_client.publish('hermes/tts/say', json.dumps({'text': text}))


def parse_open_weather_map_forecast_response(response, location):
    '''
    Parse the output of Open Weather Map's forecast endpoint
    '''
    today = fromtimestamp(response["list"][0]["dt"]).day
    today_forecasts = filter(lambda forecast: fromtimestamp(forecast["dt"]).day==today, response["list"])

    all_min = [x["main"]["temp_min"] for x in today_forecasts]
    all_max = [x["main"]["temp_max"] for x in today_forecasts]
    all_conditions = [x["weather"][0]["main"] for x in today_forecasts]
    rain = filter(lambda forecast: forecast["weather"][0]["main"] == "Rain", today_forecasts)
    snow = filter(lambda forecast: forecast["weather"][0]["main"] == "Snow", today_forecasts)

    return {
        "location": location,
        "inLocation": " in {0}".format(location) if location else "",         
        "temperature": int(today_forecasts[0]["main"]["temp"]),
        "temperatureMin": int(min(all_min)),
        "temperatureMax": int(max(all_max)),
        "rain": len(rain) &gt; 0,
        "snow": len(snow) &gt; 0,
        "mainCondition": max(set(all_conditions), key=all_conditions.count).lower()
    }


def get_weather_forecast(slots):
    '''
    Parse the query slots, and fetch the weather forecast from Open Weather Map's API
    '''
    location = slots.get("weatherForecastLocality", None) \
            or slots.get("weatherForecastCountry", None)  \
            or slots.get("weatherForecastRegion", None)  \
            or slots.get("weatherForecastGeographicalPOI", None) \
            or DEFAULT_CITY_NAME
    forecast_url = "{0}/forecast?q={1}&amp;APPID;={2}&amp;units;={3}".format(
        WEATHER_API_BASE_URL, location, WEATHER_API_KEY, UNITS)
    r_forecast = requests.get(forecast_url)
    return parse_open_weather_map_forecast_response(r_forecast.json(), location)


def add_warning_if_needed(response, weather_forecast):
    if weather_forecast["rain"] and weather_forecast["mainCondition"] != "rain":
        response += ' Be careful, it may rain.'
    if weather_forecast["snow"] and weather_forecast["mainCondition"] != "snow":
        response += ' Be careful, it may snow.'
    return response


if __name__ == '__main__':
    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message
    mqtt_client.connect(HOST, PORT)
    mqtt_client.loop_forever()

复制到你的 Raspberry Pi 3 上:

$ scp <path_to_handler> pi@raspberrypi.local:.

并在,第三个终端窗口中运行你的处理程序:

$ python handler.py

🎯 验证步骤3

我们现在可以重现我们已经做了什么,以此来验证步骤2,说:“Hey Snips”,然后“How’s the weather today in Paris?”。你的助手现在应该回应你了!

步骤4:Next

您一直使用的所有代码都是非专用的免费代码。随意个性,欢迎在 www.console.snips.ai 上为您的助手添加新意图,并探索新的黑客!特别是,如果你进入家庭自动化,你可能会喜欢 Snips 和家庭助理平台可以一起使用!

对于我们来说,我们有一些新功能即将推出,我们认为您会喜欢:法语,西班牙语,意大利语和德语的端到端支持,定制唤醒词以及对 Raspberry Pi 3 等其他平台的支持!我们也在努力给我们的助手一个更好的声音。

在此期间,我们很乐意通过我们的 Slack 社区 听取您的意见。如果您有反馈意见,请随时给我们打电话!🤖😜✨

原文链接:https://medium.com/snips-ai/build-a-weather-assistant-with-snips-4253541f1684

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.