LoginSignup
2
2

More than 5 years have passed since last update.

Alexaからスマートホーム家電を時間差をつけて制御する (その1) 〜 Alexaで時刻情報を取得してSlackにポストする

Last updated at Posted at 2019-02-16

やりたいこと

自宅のスマートホーム化計画を進めていますが、Alexaからスマート家電を操作する際の自由度が思いのほか低いなと感じました. 例えば、スマートライトであるHueの単純なON/OFFはアレクサから操作できるが、起床ルーチン (6:00にセットすると5:45からライトがだんだん明るくなる、など) はAlexaから設定できない、など. 私が具体的にやりたかったのは、例えば、

  1. Amazon Echoに「アレクサ、明日の朝7:00に起こして」とお願いする.
  2. Philips Hueを6:45から7:00にかけて徐々に明るくする.
  3. Nature Remo経由で、冷房/暖房を6:30にONにする.
  4. Amazon Echoのアラームを7:00に設定する.

というもの. 起床時間に対して前もって各家電をONにする、というのが主な目的です. このうち、4. 以外は実現の目処が立ったので作業ログを残します (Alexaカスタムスキル経由でのアラーム設定は現状許可されていないようです). ちなみに、Alexaの定型ルーチンでも似たようなことができるはずですが、以下のような制約が出ると考え、採用しませんでした

  • 声 (アレクサ、〜時に起こして) で時刻を設定することができない (Alexa App経由で時刻を変更する必要がある).
  • Hueの起床ルーチンを設定できない (指定の時刻になったらいきなり明るくなる).
  • 起床時間のXX分前から冷房/暖房をつける、という設定ができない. Alexa Appで起床時刻のXX分前から定型アクションを開始し、冷房/暖房をつけた後、定型アクションをXX分待機させ、その後Hueライトをつける、とすれば似たような挙動にはなるが、起床時刻のXX分前の時刻を計算して設定する必要があり、面倒.

作るものの全体像

20190201_SmartHome_WakeUpRoutine-4.png

これのAlexa Custom SkillとLambdaの部分を作ります. (その2はこちら) 宅内サーバを立てているのは、HueのAPIがLocalのものしかないからです.

Alexa Custom Skill

発話の中から時刻情報を抽出するためのスキル「朝のルーティーン」を実装します.

参考情報

Alexa Custom Skillの作成・実装方法
Amazon Echo (Alexa) のSkillの開発に必要な基本概念を押さえる
Alexaスキル 開発チュートリアル

Alexa Custom Skillのテスト・実機へのインストール方法
Alexaスキルのテスト方法

カスタムインテントの設定

  1. 時刻を取得するためのカスタムスロットの設定
    timeslot.png

  2. 時刻を取得するためのカスタムインテントの設定
    settimeintent.png

  3. 時刻設定"オフ"を取得するためのカスタムスロットの設定
    setoffintent.png

AWS Lambda関数の実装

全体

lambda.png

事前準備
* Slack Incoming WebhookのURLを取得しておく. (SlackのWebhook URL取得手順)
* Lambdaの環境変数としてAPP_ID (スキルID) とSLACK_WEBHOOK_PATH (上で取得したURLのhttps://hooks.slack.comより後) を設定しておく.

関数コード

Alexaで応答する部分をSlackのwebhookを実行する関数のコールバックとして実行するのがポイントでしょうか. (そうしないとwebhookが完了する前にlambdaが終了してしまった)

'use strict';

const Alexa = require('alexa-sdk');
const https = require('https');

// Get ENV variables.
const APP_ID = process.env.ALEXA_APP_ID;
const slackWebhookPath = process.env.SLACK_WEBHOOK_PATH;

const languageStrings = {
    'ja': {
        translation: {
            TALKSCRIPTS: [
                '朝のルーティーンの設定を行います。例えば、アレクサ、朝のルーティーンで六時二十分に起こして、などです。',
            ],
            SKILL_NAME: '朝のルーティーン',
            HELP_MESSAGE: '起床予定時間を伝えると、朝のルーティーンが自動で設定されます',
            HELP_REPROMPT: '朝のルーティーンを設定しますか',
            STOP_MESSAGE: 'わかりました。',
        },
    },
};

const handlers = {
    'LaunchRequest': function () {
        this.emitWithState('ExplanationIntent');
    },
    'ExplanationIntent': function () {
        const talkscripts = this.t('TALKSCRIPTS');

        // Create speech output
        const speechOutput = talkscripts.join('');
        this.emit(':tellWithCard', speechOutput, this.t('SKILL_NAME'), speechOutput);
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = this.t('HELP_MESSAGE');
        const reprompt = this.t('HELP_MESSAGE');
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', this.t('STOP_MESSAGE'));
    },
    'AMAZON.StopIntent': function () {
        this.emit(':tell', this.t('STOP_MESSAGE'));
    },
    'SetTimeIntent': function () {
        // Get intent object.
        let intent = this.event.request.intent;

        // Get slot value.
        var time = intent.slots.Time.value;

        // Triger Slack post webhook.
        trigerSlackWebhook(time, () => {
            // Generate speech text.
            const speechOutput = time + 'に朝のルーティーンを設定しました.';
            this.response.cardRenderer(this.t('SKILL_NAME'), speechOutput);
            // Make speech.
            this.response.speak(speechOutput);
            this.emit(':responseReady');
        });
    },
    'SetOffIntent': function () {
        // Triger Slack post webhook.
        trigerSlackWebhook('OFF', () => {
            // Generate speech text.
            const speechOutput = '朝のルーティーンをオフにしました.';
            this.response.cardRenderer(this.t('SKILL_NAME'), speechOutput);
            // Make speech.
            this.response.speak(speechOutput);
            this.emit(':responseReady');
        });
    },
};

function trigerSlackWebhook (designatedTime, callback) {

    let postData = {
        "text": designatedTime,
    };
    let postDataStr = JSON.stringify(postData);

    const options = {
        hostname: 'hooks.slack.com',
        port: 443,
        path: slackWebhookPath,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': postDataStr.length
        },
    };

    const slackWebhook = https.request(options, (res) => {

        var responseString = "";

        console.log('statusCode:', res.statusCode);
        console.log('headers:', res.headers);

        res.on('data', (d) => {
            responseString = d;
        });

        res.on('end', () => {
            console.log(responseString);
            callback();
        });
    });

    slackWebhook.on('error', (e) => {
        console.error(e);
    });

    slackWebhook.write(postDataStr);
    slackWebhook.end();
}

exports.handler = function (event, context) {
    const alexa = Alexa.handler(event, context);
    alexa.appId = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.resources = languageStrings;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

テスト

alexa_skill_test.png
以下のようにSlackに投稿されることを確認し、β版として実機にインストールすれば完了です.

slack.png

2
2
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
2
2