Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9138

ESP32をAlexaでLチカする(1):これから作るものと環境セットアップ

$
0
0
ESP32をAlexaでLチカします。 ESP32をAlexaのスマートホーム化することで、声で操作もできますし、AndroidやiPhoneアプリにインストールしたAlexaアプリでも、ESP32や周辺デバイスを操作したり状態を確認することができるようになります。 以下のことができるようにします。 ・ESP32に接続したENVユニットで、温度を取得して表示します。 ・ESP32に搭載されている加速度センサを取得して表示します。 ・ESP32に接続したガスセンサユニットで、二酸化炭素濃度を取得して表示します。 ・ESP32に接続したRGB LEDユニットをカラー照明として操作します。 ・ESP32に接続したサーボハットを回転操作します。 ・ESP32に搭載されているLEDをスイッチとして操作します。 ・ESP32のサイドボタンの押下状態を取得して表示します。 ・ESP32のメインボタンをドアベルとして押下したことをAlexaに通知してもらいます。 何を言っているかわかりにくいかもしれませんが、あとで示すAlexaアプリの画面を見るとわかるかと思います。 全体構成は以下のようになっています。 いくつか補足します。 ・Alexaスマートホーム化するには、Alexaサーバと連携するためにAWS Lambdaにロジックを実装する必要があります。ですが、そこでデバッグするのは面倒なので、イントラネットに立ち上げたNode.jsサーバに転送して処理しています。 ・Alexaスマートホーム化するには、OpenID Connectによるユーザ認証が必要です。自分でサーバを立ち上げたりAWS Cognitoを使うのでもよいですが、今回はLINEサーバを使わせていただきました。 ・ESP32のLチカは、ESP32とMQTTを介して連携します。(ESP32をWebAPIサーバにもできたのですが、使っているとリブートが発生したりと不安定だったので止めました。) ・Node.jsサーバとMQTTの間に中継サーバを立てて、REST API呼び出しとMQTT Publish/Subscribeを中継するようにしました。それにより、Node.jsサーバはREST API呼び出しでよくてMQTTを意識する必要がなくなるのと、中継サーバは汎用的なので他用途でも使えます。 ただし、大事なのはalexa→AWS Lambda→Node.jsサーバ の部分なので、以降ではこの部分を説明します。特に、Node.jsの部分が大事です。 ソースコードもろもろはGitHubに上げておきました。 poruruba/AlexaSmartHome_Test ESP32の接続構成 今回はESP32として、M5StickCを使います。 さらに、スマートホームっぽくするため、いくつかM5ユニットを追加しています。 M5StickCに搭載されているデバイスと、Grove端子にI2Cハブを介して3つのI2Cデバイスを接続しています。また、GPIO端子にも(排他ですが)2つのデバイス接続を試します。 いい機会なので、各デバイスのスペックをまとめておきます。 ・M5StickC https://www.switch-science.com/catalog/6350/ ・ボタンA(メインボタン) GPIO37 ・ボタンB(サイドボタン) GPIO39 ・Wire SDA:GPIO32、SCL:GPIO33 ・Wire1 SDA:GPIO21、SCL:GPIO22 ・LED GPIO10 ・加速度センサ I2C MPU6886 アドレス:0x68 接続先:Wire1 ・Ledc(PWM) GPIO26 ※Servo Hat接続時 ・環境センサユニット https://www.switch-science.com/catalog/5690/ I2C DHT12(温湿度) アドレス:0x5C BMP280(気圧) アドレス:0x76 接続先:Wire ・ガスセンサユニット https://www.switch-science.com/catalog/6619/ I2C SGP30 アドレス:0x58 接続先:Wire ・Digital Ligth Sensor https://www.switch-science.com/catalog/1174/ I2C TSL2561 アドレス:0x29 接続先:Wire ・Servo Hat https://www.switch-science.com/catalog/6076/ ES9251II GPIO26にPWMで接続 ※RGB LEDユニットと排他 ・RGB LEDユニット https://www.switch-science.com/catalog/6550/ SK6812 GPIO026にGPIOで接続 ※Servo Hatと排他 Alexaアプリでの見え方 構築完了後は、Alexaアプリで以下のように見えるようになります。そう、これが目標です。 ボタンBの押下状態。サイドボタンを押している状態ではオフと表示が切り替わります。 ボタンA押下時に反応するドアベル。この画面はあじけないですが、「ドアベル押下をアナウンス」をOnにしてからボタンAを押すと、Echo等のスマートスピーカが「ドアベルのところに誰かいます」としゃべります! Digital Ligth Sensorで輝度表示。周りの明るさのレベルに応じて明るさのパーセント表示が変わります。 環境センサユニットで温度表示 LEDの点灯状態。M5StickCのLEDを点灯・消灯できます。 加速度センサとガスセンサの二酸化炭素濃度の表示。M5StickCを傾けると加速度の数値が変わります。 RGB LEDユニットでカラー照明。RGB LEDの色変えることができます。 Servo Hatで回転角度を操作。ゲージを操作すると、M5StickCに接続したサーボが回転します。 スマートホームスキルを登録 それでは、まずはスマートホームスキルの準備から始めます。 Alexa developer Consoleにスマートホームスキルを登録します。 Alexa developer Console 「スキルの作成」ボタンを押下します。 スキルに追加するモデルは「スマートホーム」を選択し、スキルのバックエンドリソースをホスティングする方法は、ユーザ定義のプロビジョニングのみ選択できます。 次の画面で連携するAWS Lambdaの設定をします。 ちょっとここで、ブラウザの別タブを開いて、Lambdaの設定をします。スキルIDはLambda設定で使うので覚えておきます。 AWSのLambdaの画面において、右上のリージョンを「オレゴン」にします。 「関数の作成」ボタンを押下して、関数を追加します。 転送しかしないので、ランタイムは何でもよく、Node.js 14.xを選択しました。 それでは、ロジックを実装していきます。実装は以下のみです。転送しかしていないので。。。 AlexaSmartHome_Test\Node.js\AlexaSmartHome\api\controllers\alexahome_forwarder\index.js 'use strict'; const { URL, URLSearchParams } = require('url'); const fetch = require('node-fetch'); const Headers = fetch.Headers; exports.handler = async (event, context, callback) =>{ console.log(JSON.stringify(event)); console.log(context); var response = await do_post(process.env.FORWARD_URL, { event: event, context: context }); console.log(JSON.stringify(response.body)); callback(null, response.body); } function do_post(url, body) { const headers = new Headers({ "Content-Type": "application/json" }); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; return response.json(); }); } ただし、npmモジュール「node-fetch」を使っていますので、以下の通りにZIP化してアップロードします。 # mkdir alexahome_forwarder # cd alexahome_forwarder # npm init -y # vi index.js # zip index.zip . 出来上がったZIPファイルをAWS Lambdaにアップロードします。 環境変数に以下を指定します。  キー名:FORWARD_URL  値:https://【転送先のドメイン名】/alexahome-forward 次に、トリガを追加から、Alexa Smart Homeを選択し、先ほど覚えたスキルIDをアプリケーションIDのところに指定します。 これで完成ですが、Alexa側にも設定が必要で、AWS Lambdaの関数のARNをメモっておきます。もう一度以下の画面に戻って、極東のチェックをオンにして、デフォルトのエンドポイントと極東のエンドポイントに、AWS Lambdaの関数のARNを指定して、保存ボタンを押下します。 次に、アカウントリンクを選択します。 このうち、Alexaのリダイレクト先のURLが3つほど表示されています。認証サーバとして利用するLINEサーバに設定が必要なので、メモっておきます。  https://pitangui.amazon.com/api/skill/link/XXXXXXXXXXXXXX  https://alexa.amazon.co.jp/api/skill/link/XXXXXXXXXXXXXX  https://layla.amazon.com/api/skill/link/XXXXXXXXXXXXXX LINEにOpenID Connect設定 ブラウザから、LINEデベロッパーコンソールを開きます。 LINEデベロッパーコンソール プロバイダを選択します。プロバイダを作成していなければ作成します。 以下のプロバイダの設定画面で、新規チャネルを作成します。 作成するのは、LINEログインです。すでに作成済みであれば、それでよいです。 登録されるといろいろ情報が表示されます。 使うのは、以下です。 ・チャネルID ・チャネルシークレット チャネル基本設定のタブにあります。 また、LINEログイン設定のタブにあるコールバックURLに、Alexa Developer ConsoleでメモったAlexaのリダイレクト先のURLを指定します。 Alexa develper consoleにLINEサーバを設定する 以下の画面に戻って入力します。 ・Web認証画面のURL:https://access.line.me/oauth2/v2.1/authorize ・アクセストークンのURL:https://api.line.me/oauth2/v2.1/token ・ユーザーのクライアントID:LINEのチャネルID ・ユーザーのシークレット:チャネルシークレット ・ユーザーの認可スキーム:HTTP Basic認証 ・スコープ:openid profile email(任意) ドメインリストとデフォルトのアクセストークンの有効期限は空白でよいです。 次に、アクセス権限を選択し、Alexaイベントを送る、のスイッチをOnにします。 また、AlexaクライアントIDとAlexaクライアントシークレットは後で使うので覚えておきます。 一応これで、設定はできました。 ベータテスト公開設定 AlexaアプリやEchoなどの本物のデバイスで試行で試すには、まずベータテスト公開設定の状態にしておく必要があります。 公開タブを選択します。 いろいろ入力項目がありますが、ベータテストをするには、以下の項目を適当に埋めます。 ・公開名 ・説明 ・詳細な説明 ・サンプルフレーズ ・小さなスキルアイコン ・大きなスキルアイコン ・カテゴリ ・プライバシポリシURL ・プライバシとコンプライアンス ・テストの手順 最後に、検証タブにおいて、実行ボタンを押下して以下のように検証OKとなればよいです。 ベータテストは、公開タブの公開範囲にあるベータテストで設定します。 ベータテスタとして、テスト対象のAlexaアプリやEchoスマートスピーカの設定に使ったAmazonアカウントのメールアドレスを指定します。(これが違っていると、いつまでたっても、Alexaアプリから今回作成したスマートホームスキルが選択肢に出てきません) Amazonアカウントのメールアドレスを追加すると、本人にメールが届きます。 メールが届いた後の操作は、Node.jsサーバのセットアップが終わった後にします。 MQTTブローカのセットアップ 以下を参考に立ち上げます。手抜きですみません。 AWS IoTにMosquittoをブリッジにしてつなぐ M5StickCのセットアップ Arduinoフォルダに一式あります。PlatformIOで作成していますので、Visual Studio Codeから書き込みます。 以下の部分を書き換えます。 mqttApi.cpp ・const char *MQTT_BROKER_URL = "【MQTTブローカのドメイン名】"; // MQTTブローカのドメイン名 main.cpp ・const char *wifi_ssid = "【WiFiアクセスポイントのSSID 】"; // WiFiアクセスポイントのSSID ・const char *wifi_password = "【WiFiアクセスポイントのパスワード】"; // WiFiアクセスポイントのパスワード ・const char *MQTT_PUBLISH_PUSH_TOPIC = "esp32_webapi_push"; // MQTTトピック名 ・const char *MQTT_PUBLISH_PUSH_URL = "https://【中継サーバのドメイン名】/alexahome-push"; //中継サーバのURL(Push用) Node.jsサーバのセットアップ Node.jsフォルダに一式あります。 Node.jsサーバと中継サーバの両方を実装してあります。 # cd node.js/AlexaSmartHome # npm install # mkdir data # mkdir data/alexa_smarthome/ # vi .env # node app.js .envファイルには以下を記載します。 AlexaSmartHome_Test\Node.js\AlexaSmartHome\.env PORT=【HTTPポート番号】 MQTT_BROKER_URL=mqtt://【MQTTブローカのホスト名】:1883 HTTPではなくHTTPSの場合は、certフォルダを作成してHTTP証明書を配置し、.envファイルには以下を記載します。 AlexaSmartHome_Test\Node.js\AlexaSmartHome\.env SPORT=【HTTPSポート番号】 エンドポイント・トピック名情報 Node.jsサーバのエンドポイント /alexahome-forward:AWS Lambdaからの転送を受け取ります。 /alexahome-push:ESP32側からの通知を受け取ります。 中継サーバのエンドポイント /mqttapi/*:Node.jsサーバからESP32を操作するためのリクエストを受け取ります。 MQTTトピック名  mqttapi/XXX.XXX.XXX.XXX:中継サーバからのリクエストをESP32が受け付けます。XXXXはESP32のIPアドレスです。  esp32_webapi_notify:ESP32へのリクエストに対する応答を受け取ります。  esp32_webapi_push:ボタン押下など、ESP32で発生したリクエストを中継サーバが受け取ります。 ちなみに、中継サーバは、WebAPI呼び出しとMQTT Publish/Subscribeの中継をしています。 こんな感じの実装です。 AlexaSmartHome_Test\Node.js\AlexaSmartHome\api\controllers\mqttapi_proxy\index.js 'use strict'; const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/'; const Response = require(HELPER_BASE + 'response'); const MQTT_BROKER_URL = process.env.MQTT_BROKER_URL; const MQTT_SUBSCRIBE_NOTIFY_TOPIC = "esp32_webapi_notify"; const MQTT_SUBSCRIBE_PUSH_TOPIC = "esp32_webapi_push"; const DEFAULT_TIMEOUT = 60 * 1000; const mqtt = require('mqtt') const fetch = require('node-fetch'); const Headers = fetch.Headers; let mqtt_client = mqtt.connect(MQTT_BROKER_URL); let mqtt_msgId = 0; let requestMap = new Map(); exports.handler = async (event, context, callback) => { var body = JSON.parse(event.body); var endpoint = event.path.substr("/mqttapi".length); var topic = event.queryStringParameters.topic; var oneway = event.queryStringParameters.oneway; if( body.topic ) topic = body.topic; if( body.oneway ) oneway = body.oneway; var msgId = ++mqtt_msgId; cleanUpRequestMap(); var message = { endpoint: endpoint, topic: MQTT_SUBSCRIBE_NOTIFY_TOPIC, msgId: msgId, params: body, oneway: oneway }; if( !message.oneway ){ return new Promise((resolve, reject) =>{ requestMap.set(msgId, { resolve, reject, created_at: new Date().getTime() }); mqtt_client.publish(topic, JSON.stringify(message)); }); }else{ mqtt_client.publish(topic, JSON.stringify(message)); return new Response({status: "OK" }); } }; exports.trigger = async (event, context) => { if( context.topic == MQTT_SUBSCRIBE_NOTIFY_TOPIC){ var body = JSON.parse(event); if( requestMap.get(body.msgId) ){ var obj = requestMap.get(body.msgId); obj.resolve(new Response({ status: body.status, result: body.result })); requestMap.delete(body.msgId); }else{ console.error('requestMap not found'); } cleanUpRequestMap(); }else if( context.topic == MQTT_SUBSCRIBE_PUSH_TOPIC ){ var body = JSON.parse(event); console.log("MQTT_SUBSCRIBE_PUSH_TOPIC", body); await do_post(body.url, body); } }; function cleanUpRequestMap(){ var now = new Date().getTime(); requestMap.forEach((value, key) => { if( value.create_at < now - DEFAULT_TIMEOUT ){ value.reject("timeout"); requestMap.delete(key); console.log("timeout deleted(" + key + ")"); } }); } function do_post(url, body) { const headers = new Headers({ "Content-Type": "application/json" }); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; return response.json(); }); } 次回 今回はここまで。 セットアップのところはかなり速足でしたが、大事な部分は次回のNode.jsサーバの実装部分です。その中のESP32との連携部分は環境に合わせて作り替えればよいので、今回のセットアップ部分はあまり気にしなくてもよいかな。 以上

Viewing all articles
Browse latest Browse all 9138

Trending Articles