Alexaに気温と二酸化炭素濃度を教えてもらう

Alexaに気温と二酸化炭素濃度を教えてもらう
公開日時
更新日時

NestJSの練習がてら、家にあるセンサー情報を集約するAPIを作成した。

APIは↓のように最新のセンサー情報を返すだけのシンプルなものになっている。

{
  temperature: 28.15,
  humidity: 67.6152,
  pressure: 1011.04,
  pm25: 0,
  co2: 913
}

今回はセンサー情報取得APIを使って、Alexaにセンサー情報を教えてもらえるようにする。

Alexa Skillの作成

  • Alexa Developer Consoleにアクセス
  • 「スキルの作成」を選択
  • スキル名に呼び出したいワードを入力(今回は「気温」で呼び出せるようにした)
alexa1
  • ホスティング先は「Alexa-Hosted」を選択
alexa2
  • テンプレートは「Hello Worldスキル」を選択
alexa4

実装

  • 「コードエディタ」メニューを開く
  • package.jsonのdependenciesにaxiosを追加
  "dependencies": {
    "axios": "^0.20.0",
    "ask-sdk-core": "^2.6.0",
    "ask-sdk-model": "^1.18.0",
    "aws-sdk": "^2.326.0"
  }
  • index.jsはHello WorldテンプレートをベースにLaunchRequestHandlerのみ編集
  • センサー情報を取得して応答するように変更し、repromptを外して一回のみ起動するように
const Alexa = require('ask-sdk-core');
const axios = require("axios");

const getSensorData = async () => {
  try {
    const response = await axios.get(
      "https://{sensor-api-url}"
    );
    return response.data;
  } catch (error) {
    console.error(error);
  }
};

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    async handle(handlerInput) {
        const sensor = await getSensorData();
        const speakOutput = `リビングのCO2濃度は${sensor.co2}ppmです。温度は${Math.round(sensor.temperature * 10) / 10}度、湿度は${Math.round(sensor.humidity * 10) / 10}パーセントです。気圧は${Math.round(sensor.pressure)}ヘクトパスカルです。`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'You can say hello to me! How can I help?';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
                || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'Goodbye!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse();
    }
};

const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        console.log(`~~~~ Error handled: ${error.stack}`);
        const speakOutput = `Sorry, I had trouble doing what you asked. Please try again.`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler, // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
    )
    .addErrorHandlers(
        ErrorHandler,
    )
    .lambda();
  • コードの実装が終わったら「デプロイ」を実行

確認

  • 「テスト」メニューを開き、ステータスを「開発中」に変更する
  • Alexaシミュレーターに「気温」を入力すると、センサー情報を教えてくれる
alexa3

開発中のスキルはEchoやEcho Dotでも有効になるので、この状態で「Alexa 気温」と言えばセンサー情報を教えてくれる。


Related #aws

Step Functionsステートマシンから別のStep Functionsを呼び出す

複雑なステートマシンを小さいステートマシンに分割しておけばテストと確認がやりやすくなる

マネージメントコンソール上のエディタでLambdaのコードを書く際にnpmライブラリを追加したい

ローカルでライブラリをインストールしてからインポートする必要があった

Amazon API Gatewayのタイムアウト設定は最大29秒まで

上限緩和もできないので注意

MFA必須のスイッチロールアカウントでaws cliを使う

switch role用のprofileを追加する

AWSのコスト異常検出を設定する

意図しない課金を防ぐためにとりあえず設定しておくと良さそう