69
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GoogleHomeでポケモン図鑑作ってみた

Posted at

Google Homeを購入したので、何か作ってみたくて作りました。
ざっと2時間程度で作れました。

GitHub: [google-home-pokemon-dictionary]
(https://github.com/reireias/google-home-pokemon-dictionary)

概要

  • 「ポケモン図鑑 ピカチュウ」とGoogle Homeに聞くとポケモン図鑑の説明をしゃべってくれる

  • 「ピカチュウ。ねずみポケモン。はじめて見るものには〜」というようにアニメのポケモン図鑑風に返してくれる

    • ただし、音声はGoogle Homeのそれ

構成

こんな感じです。
GoogleHome.png

IFTTTのGoogle Assistantを使うと、複雑なレスポンスを即座にGoogle Homeに喋らすことはできません。
そこで、MQTTというIoTデバイスでよく使われる軽量なプロトコルを利用して自宅PCに通知を送っています。

beebotteとい無料のMQTTホスティングサービスを利用し、自宅PC上のnodeからメッセージを監視しています。
メッセージにはポケモンの名前が渡ってきますので、pokeapiというAPIサービスを利用して図鑑説明を取得しています。

最後はnodeのgoogle-home-notifierを利用して、Google Homeに任意のセリフを喋らせています。

ちなみに、ほぼ「IFTTTとBeebotteを使ってGoogleHomeからRaspberryPiを操作する」の構成をパクっています。

動作環境

以下の環境で確認しています。

  • Linux Mint 18.2
  • node v9.5.0

構築手順

BeeBotte

まずはGoogle Homeと自宅PCとをつなぐためのBeeBotteというMQTTのサービスを設定します。
と、言っても複雑な設定はなく、アカウントを作った後は、適当な名前のchannelとresourceを作成して終わりです。
以下のように作れるかと思います。

Screenshot from 2018-02-15 21-55-18_censored.jpg

後で利用するのでChannel Tokenの値を控えておきます。

IFTTT

New Appletからappletを作成しています。

thisにはGoogle Assistantを利用します。
Screenshot from 2018-02-15 21-47-06.png

「任意のフレーズ + ポケモンの名前」で起動したいので、左から3番目のSay a phrase with a text ingredientを選択します。
Screenshot from 2018-02-15 21-47-47.png

設定はこんな感じ。
Screenshot from 2018-02-15 21-50-13.png

あとの設定は適当で大丈夫です。(たぶん)

続いてthatの設定です。
BeeBotteのアプリはIFTTTには無いのでWebhooksを使います。
Screenshot from 2018-02-15 21-52-37.png

設定は次のようになります。(BeeBotteの情報を入力します。)

項目
URL http://api.beebotte.com/v1/data/publish/${channel}/${resource}?token=${channel_token}
Method POST
Content Type application/json
Body {"data": "{{TextField}}"}

node

最後はBeeBotteからメッセージを取り出してポケモン図鑑の説明を取得し結果をGoogleHomeに通知する部分です。

ソースコードは次の通りです。

main.js
'use strict';
const beebotte = require('beebotte');
const config = require('config');
const fs = require('fs');
const googlehome = require('google-home-notifier');
const request = require('request');

const searchUrl = 'https://pokeapi.co/api/v2/pokemon-species/';
const nameIdMap = JSON.parse(fs.readFileSync('./pokemon.json', 'utf-8'));

/**
 * main function
 */
const main = () => {
    googlehome.ip(config.googlehome.ip, config.googlehome.language);
    subscribe();
};

/**
 * subscribe beebotte channel
 */
const subscribe = () => {
    let transport = {
        type: 'mqtt',
        token: config.beebotte.token,
    };
    let channel = config.beebotte.channel;
    let resource = config.beebotte.resource;
    let client = new beebotte.Stream({transport: transport});

    client.on('connected', () => {
        client.subscribe(channel, resource, (message) => {
            let name = message.data;
            search(name, notify);
        }).on('subscribed', (sub) => {
            console.info('subscribed.');
        });
    });
};

/**
 * search flavor text from pokeapi
 * @param {string} name The name of target
 * @param {function} callback The callback function
 */
const search = (name, callback) => {
    if (nameIdMap[name]) {
        let options = {
            url: searchUrl + nameIdMap[name],
            json: true,
        };
        request.get(options, (error, response, body) => {
            if (response.statusCode == 200) {
                let message = createNotifyMessage(body);
                callback(message);
            } else {
                console.error(response);
                callback('エラーが発生しました。');
            }
        });
    } else {
        callback(name + 'は見つかりませんでした。');
    }
};

/**
 * create message for notify google home
 * @param {object} body Search response body (json)
 * @return {string}
 */
const createNotifyMessage = (body) => {
    let language = config.pokeapi.language;
    let ftLanguage = config.pokeapi.flavorText.language;
    let version = config.pokeapi.flavorText.version;
    let names = body.names.filter((name) => language == name.language.name);
    let genera = body.genera.filter((genus) => language == genus.language.name);
    let flavorTexts = body.flavor_text_entries.filter((text) => {
        return ftLanguage == text.language.name && version == text.version.name;
    });
    let message = names[0].name + '' + genera[0].genus + ''
        + flavorTexts[0].flavor_text;
    return message;
};

/**
 * notify to google home
 * @param {string} message The message for notification
 */
const notify = (message) => {
    console.info(message);
    googlehome.notify(message, (response) => {
        console.info(response);
    });
};

if (require.main === module) {
    main();
}

configファイルの中身はこんな感じです。
GoogleHomeのIPやBeeBotteの各種設定値を書きます。

config/default.yaml
googlehome:
  ip: 'TODO your google home ip'
  language: 'ja'
pokeapi:
  language: 'ja'
  flavorText:
    language: 'ja'
    version: 'omega-ruby'
beebotte:
  channel: 'your channel'
  resource: 'your resource'
  token: 'your token'

BeeBottからのメッセージの取得はbeebotteモジュールを使っています。

利用するAPIの都合上、ポケモンの名前 -> 図鑑番号に変換するため、対応が書かれたローカルファイルを読み込んで変換しています。

Google Homeへの通信はgoogle-home-notifierを使って行っています。
自宅の環境の都合でIP指定で接続していますが、デバイス名でも接続できるようです。

動かしてみる

起動

$ node main.js

私 「OK Google、ポケモン図鑑 ピカチュウ」

Google Home 「ピカチュウ。ねずみポケモン。はじめて見るものには電撃を当てる。黒こげのきの棒が落ちていたらそれは電撃の強さを間違えた証拠だよ。」

やったぜ!

最後に

ラズパイ買わなきゃ

69
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?