Mi・LightをAlexa経由で音声操作

2020年12月31日

今回はMi・LightをAlexaを使用した音声による操作を行うところを試してみたいと思います。

この方法は自宅サーバー機が用意できることが前提なので注意してください。

テストするだけであれば、普段使いのPCでも構いません。

とりあえず動作させるところまでが目標なので普段使いのWindows PCに導入して試します。



準備と操作に必要な情報

プログラムから操作するには専用コントローラを入手する必要があります。

コントローラの設定は、この記事で紹介しました。

コントローラにどのようなデータを流し込めばいいかは、こちらを参照してください。

Node.jsをインストール

下記のサイトよりインストーラを入手しNode.jsをインストールします。

LTS版をインストールするのでいいと思います。

インストールできたらパスが通ってるか確認します。

コマンドプロンプトを開き、「node –version」のコマンドを実行します。

node --version v12.16.3

バージョンが表示されればOKです。

もしコマンドが実行できなければインストールフォルダにパスを通すか、Node.jsのコマンドを実行する際にcdコマンドでインストールフォルダへ移動してから実行するようにすれば問題ありません。

Node-REDをインストール

次にNode-REDをインストールします。

「npm install -g –unsafe-perm node-red」を実行します。

npm install -g --unsafe-perm node-red .. ... + node-red@1.0.6 removed 4 packages and updated 25 packages in 25.433s

Node-REDがインストールできたら、次のコマンドで起動してみましょう。

node-red start .. ... 4 May 18:55:26 - [info] Starting flows 4 May 18:55:26 - [info] Started flows 4 May 18:55:27 - [info] Server now running at http://127.0.0.1:1880/

起動したらブラウザでサーバーURL「http://127.0.0.1:1880/」へ接続してみましょう。

下記サイトにNode.jsからNode-REDのインストールまでが掲載されていますので、上手くいかなければ覗いてみてください。

Node-RED Alexa Home Skill Bridgeの設定

まずはNode-REDにnode-red-contrib-alexa-home-skillをインストールします。

npm install node-red-contrib-alexa-home-skill .. ... + node-red-contrib-alexa-home-skill@0.1.17 updated 1 package and audited 1999 packages in 3.896s

Node-Redを起動してAlexaのノードが使用できるようになっているか確認します。

左側の一覧にAlexaの項目が追加されていればOK

次にNode-RED Alexa Home Skill Bridgeのサイトへ移動しアカウントを登録します。

アカウントを持っている方はLoginを選択し、アカウントを持っていない場合はRegisterを選択します。

ユーザー名、メールアドレス、パスワードを設定し登録してください。

※2020/05/05時点では、アカウント削除の機能が用意されていないので一回登録すると基本的に削除できませんし、編集もできません。後で後悔しないように設定して登録してください。

もし、どうしてもアカウント削除したい場合はサイト管理者へ連絡し、削除してもらう必要があります。日本人ではないので、英語で交渉する必要があります。

アカウント登録後はログインしてDevicesタブを開きます。

Add Deviceを選択

Name、Description、Actions、ApplicationTypeを設定しましょう。

Nameにデバイス名を設定します。

デバイス名は音声操作する時の名前になります。

例えば「アレクサ、電気をつけて」で反応させるならNameには「電気」と設定しましょう。

Descriptionは説明文です。今回は「テスト」としておきます。

Actionsは~をつけてや~消して、~度に設定して、などデバイスの動作にあった物を選択します。これの変更によって、Node-REDフローで取得できるパラメータが変わります。

今回はMi・Lightを操作するので「電気をつけて」と「電気を消して」の音声操作に対応したいのでOn:とOff:にチェックを入れておきましょう。

電球操作を行うので、ActionTypeはLightにしときましょうか。

以上でNode-RED Alexa Home Skill Bridgeの設定が完了です。

Alexaスキルの設定

次はAlexa側の設定です。

こちらにアクセスし、Amazonアカウントにログインします。

左のリストから「スキル」を選択しのNode-REDのスキルを有効にします。

有効にするを選択するとNode-RED Alexa Home Skill Bridgeのサイトへ飛ばされるので、先ほど作成したアカウントでログインします。

完了して戻ると、端末を検出するというポップアップが表示されます。

デバイスに「電気」が追加されていたらOK

以上で、Alexa側の設定は完了です。

試しにNode-REDのフローを作成して音声操作の内容を受信できるか見てみましょう。

テストに使用するノードは下記

・alexa home・・・音声操作したことを受信するノード
・debug・・・データをログに出力するノード
・alexa home resp・・・操作の結果をAlexaに送信するノード

です。

テストでは受信したデータをログ出力し、操作に成功したとAlexaに通知するフローを作成しました。

フローができたら、alexa homeノードをダブルクリックし、Node-RED Alexa Home Skill Bridgeのアカウントのログイン情報を入力し、リンクしておきましょう。

IDとパスワードを入力

リンクしたらDeviceは先ほど作成した「電気」を選択しておきます。

Auto Acknowledgeは自動でAlexaに応答を返すかどうかです。操作の結果を手動で行いたい場合はチェックを外し、自動で行う場合はチェックします。

自動にすると常に成功したときの動作となるようです。

この状態でデプロイを行えば準備完了。

早速「アレクサ、電気をつけて」や「アレクサ、電気を消して」と音声発話し、ログに結果が表示されるか見てみましょう。

「アレクサ、電気をつけて」ならログにtrueが表示され、「アレクサ、電気を消して」ならfalseが表示されたらOK。

実際にフローを作成する時は、このパラメータを判定して操作の処理を行います。

Node-REDでMi・Lightコントローラを操作するフローを作成

いよいよMi・Lightコントローラを操作するフローを作成します。

Node-REDはJavaScriptを使用できるので、それを駆使してコマンドデータを作成、そのあとUDP:5987へデータを送信するようにしてみたいと思います。

使用するノードは下記

・alexa home・・・音声操作したことを受信するノード
・alexa home resp・・・操作の結果をAlexaに送信するノード
・udp in・・・データを受信するノード
・udp out・・・データを送信するノード
・function・・・JavaScriptを実行できるノード
・debug・・・データをログに出力するノード
・switch・・・処理分岐ノード

ざっくりな処理の流れとしては、こんな感じになるかな

1.アレクサから音声操作内容を受信
2.Mi・Lightセッションコマンドを送信
3.Mi・Lightセッションコマンド実行結果の受信
4.セッションコマンド実行判定
 4-1.セッション開始成功→音声操作のON、OFF判定
  4-1-1.音声操作が「~点けて」だった→Mi・Light点灯コマンドデータを送信
  4-1-2.音声操作が「~消して」だった→Mi・Light消灯コマンドデータを送信
 4-2.セッション開始失敗→処理を6-2へ移す
5.Mi・Lightコマンドの実行結果の受信
6.Mi・Lightコマンドの実行判定
 6-1.実行結果が成功→アレクサに成功を送信
 6-2.実行結果が失敗→アレクサに失敗を送信

この内容を元にNode-REDフローを作成してみました。

試したい人用にNode-REDへの読み込み用JSONを掲載しておきます。

Node-REDに下記JSON読み込んで、udp out ノードのIPアドレスとAlexa Homeノードのアカウントだけ設定してもらうと動くと思います。

[
    {
        "id": "95074e3.c00b0b",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "bdde7ce7.da8c",
        "type": "alexa-home",
        "z": "95074e3.c00b0b",
        "conf": "",
        "device": "",
        "acknoledge": false,
        "name": "",
        "topic": "",
        "x": 190,
        "y": 360,
        "wires": [
            [
                "ae6ade36.1aadd",
                "9c540120.25007"
            ]
        ]
    },
    {
        "id": "ae6ade36.1aadd",
        "type": "debug",
        "z": "95074e3.c00b0b",
        "name": "debug",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "x": 450,
        "y": 300,
        "wires": []
    },
    {
        "id": "9c540120.25007",
        "type": "function",
        "z": "95074e3.c00b0b",
        "name": "function",
        "func": "//アレクサのメッセージをメモリに保存\nflow.set(\"alexa_msg\", msg);\n\n//処理段階判定フラグを立てる\nmsg.proc_num = 1;\n\n//セッション開始データを作成\nvar arr = [0x23, 0x00 , 0x00 , 0x00 , 0x16, 0x02, 0x62 , 0x3a , 0xd5 , 0xed, 0xa3, 0x01 , 0xae , 0x08 , 0x2d, 0x46, 0x61 , 0x41 , 0xa7 , 0xf6, 0xdc, 0xaf , 0xa6 , 0xa1 , 0x00, 0x00, 0x64];\nreturn {\"payload\":new Buffer(arr)};",
        "outputs": 1,
        "noerr": 0,
        "x": 460,
        "y": 360,
        "wires": [
            [
                "a06b43cf.e4ef8"
            ]
        ]
    },
    {
        "id": "5779a4cb.374c0c",
        "type": "udp in",
        "z": "95074e3.c00b0b",
        "name": "udp in",
        "iface": "",
        "port": "5987",
        "ipv": "udp4",
        "multicast": "false",
        "group": "",
        "datatype": "buffer",
        "x": 170,
        "y": 500,
        "wires": [
            [
                "70ca9d81.731474"
            ]
        ]
    },
    {
        "id": "10d069b8.ea8356",
        "type": "alexa-home-resp",
        "z": "95074e3.c00b0b",
        "x": 760,
        "y": 500,
        "wires": []
    },
    {
        "id": "a06b43cf.e4ef8",
        "type": "udp out",
        "z": "95074e3.c00b0b",
        "name": "udp out",
        "addr": "192.168.1.145",
        "iface": "",
        "port": "5987",
        "ipv": "udp4",
        "outport": "",
        "base64": false,
        "multicast": "false",
        "x": 800,
        "y": 360,
        "wires": []
    },
    {
        "id": "70ca9d81.731474",
        "type": "function",
        "z": "95074e3.c00b0b",
        "name": "function",
        "func": "//メモリに保存したアレクサからのメッセージを取得\nvar alexa_msg = flow.get(\"alexa_msg\", msg);\n\n//UDP受信データ\nvar recv = msg.payload;\n\nif ( alexa_msg.proc_num === 1 ) {\n    //セッションの受信判定処理\n    \n    //レスポンス判定用\n    var RESPONSE_START_SESSION = [\n        0x28, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02\n    ];\n    \n    //開始されたかチェック\n    for ( var i = 0;i < RESPONSE_START_SESSION.length;i++ ) {\n        if ( RESPONSE_START_SESSION[i] != recv[i] ) {\n            alexa_msg.proc_num = 0;\n            alexa_msg.payload = false;\n            \n            //失敗をアレクサに送信\n            return alexa_msg;\n        }\n    }\n    \n    //セッションIDの取得\n    var sessionId1 = recv[19];\n    var sessionId2 = recv[20];\n    \n    //処理段階判定フラグ変更\n    alexa_msg.proc_num = 2;\n    \n    if ( alexa_msg.payload ) {\n        //電気ON\n        arr = [ 0x80,0x00,0x00,0x00,0x11,sessionId1,sessionId2,0x00,0x10,0x00,0x31,0x00,0x00,0x08,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x3E];\n    } else {\n        //電気OFF\n        arr = [ 0x80,0x00,0x00,0x00,0x11,sessionId1,sessionId2,0x00,0x10,0x00,0x31,0x00,0x00,0x08,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x3F];\n    }\n    \n    //送信データの作成\n    return {\"payload\":new Buffer(arr)};\n} else {\n    //操作の受信判定処理\n    \n    var isOK = true;\n\n\t//開始通知判定用\n\tvar RESPONSE_CHECK = [\n\t\t\t0x88, 0x00, 0x00, 0x00, 0x03, 0x00\n\t];\n\n\t//処理されたかチェック\n\tfor ( var i = 0;i < RESPONSE_CHECK.length;i++ ) {\n\t\tif ( RESPONSE_CHECK[i] != recv[i] ) {\n\t\t    //失敗\n\t\t\tisOK = false;\n\t\t}\n\t}\n\t\n\t//処理結果をアレクサに送信\n    alexa_msg.proc_num = 0;\n    alexa_msg.payload = isOK;\n    return alexa_msg;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "x": 340,
        "y": 500,
        "wires": [
            [
                "f0aa7c0.4718c88"
            ]
        ]
    },
    {
        "id": "f0aa7c0.4718c88",
        "type": "switch",
        "z": "95074e3.c00b0b",
        "name": "",
        "property": "alexa_msg.proc_num",
        "propertyType": "flow",
        "rules": [
            {
                "t": "eq",
                "v": "2",
                "vt": "str"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 510,
        "y": 500,
        "wires": [
            [
                "a06b43cf.e4ef8"
            ],
            [
                "10d069b8.ea8356"
            ]
        ]
    }
]
実行結果

うまく音声で操作できていることが確認できました。
動画で使用している音声は、こちらのサイトを利用させていただきました。

操作対象のMi・Lightはスマフォアプリの一番左のリモコンでリンクしておいてください。でないと動かないです。

グループ指定などがしたい場合はfunctionノードのバイト配列を改造してあげるとできると思います。functionノードのJavaScriptはしっかりコメントも付けておいたので、わかる人にはわかると思います。

リンクやグループ指定方法などMi・Lightの基本的な設定は前回の記事に掲載していますので興味があれば覗いてみてください。


今回初めてNode-REDと使ってみましたがpythonプログラムなども呼び出せるので、ラズパイとの併用でかなり自由度の高いAlexa連携が無料で構築出来ちゃいそうですね。