【RaspberryPi】Node-REDからAlexaを発話(TTS)させる

そろそろ、Node-REDを使ってalexaを発話させてみたいなと思いました。家にはかなりのスマートフォンの第一世代があるのですが、全部つけてしまうと部屋よりも多いのでさすがに部屋に積んであるといった状態です。Node-REDで使用できると、色々な連携もできますし、先日アマゾンさんのSmartPlugも購入したこともあるので、このあたり連携できると面白いなんて思っています。

以下を参考にしながら作業を行いました。参考にしたエントリは完成度が高いので、基本的にはこのリンク通りに行うのであれば問題はなく動作できます。このエントリを読み飛ばしても大丈夫かなと思いますが、うまく行かなかった場合のみてもらえればと思います。

qiita.com

dream-soft.mydns.jp

Node-REDの新しいノードを追加する

※事前にEchoデバイスの設定は済ませておいてください。 Node-REDのパレットにはデフォルトでAlexaを操作するノードがインストールされていないので、まずはノードを追加します。

画面、右上の【三】(ハンバーガー)ボタンをクリックします。

f:id:ueponx:20200724121653p:plain

プルダウンメニューが表示されるので、その中から【パレットの管理】をクリックします。

f:id:ueponx:20200724121658p:plain

すると、ユーザー設定のパレット画面が表示されています。続いて。画面上方のタブから【ノードを追加】をクリックします。

f:id:ueponx:20200724121802p:plain

すると、追加ノードの検索画面が表示されます。

f:id:ueponx:20200724121843p:plain

ここまできたら、検索ボックスにalexa-remoteを入力します。すると検索画面の以下の2つが検索候補として表示されます。

  • node-red-contrib-alexa-remote
  • node-red-contrib-alexa-remote2

この2つのノードですが、node-red-contrib-alexa-remoteは最終メンテナンスが一年以上立っているので、比較的最近メンテナンスが行われているnode-red-contrib-alexa-remote2を追加していきます。

f:id:ueponx:20200724122227p:plain

node-red-contrib-alexa-remote2側の【ノードを追加】ボタンをクリックします

f:id:ueponx:20200724122349p:plain

するとインストール作業が開始されます。

f:id:ueponx:20200724122637p:plain

先程の【ノードの追加】の表示がグレーアウトされ【追加しました】になっていればインストール完了となります。ここまで確認したら【閉じる】ボタンをクリックします。

f:id:ueponx:20200724123009p:plain

インストールが終わるとパレットにalexa関連のノードが追加されています。

f:id:ueponx:20200724123048p:plain

flows.nodered.org

f:id:ueponx:20200728184459p:plain

Alexa RoutineノードでTTSを行う

今回は、【injectノード】と【alexa routineノード】をフローエディタにドロップして配置を行います。 配置を行ったら端子を以下の図の様に接続しておきます。

f:id:ueponx:20200724123154p:plain

【alexa routineノード】クリックしてプロパティを設定していきます。まずは認証の設定を行います。 Accountのパラメータにある、【編集】(ペンのアイコン)ボタンをクリックします。

f:id:ueponx:20200724123340p:plain

新規にAlexa Accountノードの設定を追加の画面が表示されます。ここでAlexaのアカウント情報を入力していきます。

f:id:ueponx:20200724123448p:plain

入力が必要になるのは以下のパラメータになります。

  • Name … 認証設定の名前
  • Auth Method … 認証方法(Mail/Password、Cookie、Proxyが存在しますが今回はProxyを使用します)
  • This IP … Node-REDの動作しているPCなどのIP(今回はRaspberryPiで動作させているのでそのIPアドレスとなります)
  • Port … アクセスポートとなります(デフォルト値の3456で大丈夫です)
  • Service Host … alexa.amazon.co.jp(alexaの日本ドメイン
  • Page … amazon.co.jp(アマゾンの日本ドメイン
  • Language … ja_JP(日本語)

認証方法に関してはMail/Passwordで行っているサイトが多いのですが、この設定値は今後使えなくなる可能性が高いことと、ユーザーによっては認証できないこともあるらしいので、今回はProxyを設定することにしています。

後述しますが、実はProxyの認証はこの設定だけではうまくいきません。まあ、ハマりどころです。

とりあえず、設定が終わったら上の方にある【追加】ボタンをクリックします。

f:id:ueponx:20200724123627p:plain

すると、【Alexa Routineノードの編集】画面に戻ってきます。Accountのパラメータが先程設定したNameの値になっていれば大丈夫です。

f:id:ueponx:20200728020804p:plain

ここでも設定項目するはありますが、認証部分を先に解決させるので【完了】ボタンをクリックしてプロパティ画面を閉じます。

f:id:ueponx:20200728020942p:plain

フローエディタに戻ってきたら【デプロイ】ボタンをクリックします。

f:id:ueponx:20200724123733p:plain

すると、【Alexa Routineノード】の下の部分にOpen 192.168.0.17:3456 in your browserと表示されます。まだこの時点では認証はされていません。ブラウザを使用して認証作業を行うことになります。

f:id:ueponx:20200724123814p:plain

ブラウザでhttps://【Node-REDの動作しているPCなどのIP】:3456にアクセスを行います。

例えば、Node-REDが動作しているRaspberryPiのIPアドレスが192.168.0.17であれば、 http://192.168.0.17:3456/ にアクセスしてください。

アクセスすると、Alexaの認証画面が表示されます。Node-REDの動作しているサーバーをプロキシーとしてアクセスしています。ここで認証譲情報を入力してログインをおこなってください。

f:id:ueponx:20200724123840p:plain

ユーザー名とパスワードを入力して【ログイン】ボタンをクリックします。

f:id:ueponx:20200724123914p:plain

2段階認証を使用している場合には、そのあとに2段階認証の画面もでますので、認証を行ってください。

f:id:ueponx:20200724124009p:plain

認証が正常におこなわれると、ブラウザは以下のような画面が表示されます。このアクセスによって通信上のCookieを取得して認証をしてくれます。なので実質はCookie認証を自動化しているのがProxy認証の正体となります。

f:id:ueponx:20200724124047p:plain

ですが、この認証作業を行ってもUnexpected end of JSON inputというエラーが発生します。

f:id:ueponx:20200728001452p:plain

依存ライブラリAlexa-Cookie2のソース修正を行う

このエラーをググってみると、以下のようなエントリがあったので参考にしました。

dream-soft.mydns.jp

問題は、依存しているライブラリのバージョンが代わり、変数名などの変更が行われているため、バージョンに合わせて微調整を行う必要がありました。

参考情報ではエラーが発生したライブラリのバージョンはAlexa-Cookie2 v3.2.1版となっていますが、自分が作業を行った2020.07.22の段階ではAlexa-Cookie2 v3.3.3版となっていたため、ソースコードの編集箇所が少し違っていまいた。

このライブラリの何が問題かというと、このライブラリのアクセス先のAlexaのサイトにJPドメインのものが設定できないということが問題の原因となります

では、まず依存しているライブラリのバージョンを確認します。依存しているライブラリの名前はalexa-cookie2となります。npm listコマンドでインストール一覧からalexa-cookieを検索するとバージョンが確認できます。

$ cd ~/.node-red
$ npm list|grep alexa-cookie

f:id:ueponx:20200724124113p:plain

$ cd ~/.node-red/node_modules/alexa-cookie2/
$ cp alexa-cookie.js  alexa-cookie.js.org
$ vi alexa-cookie.js

alexa-cookie.js 編集後260行目あたりに以下のデータを追加

(略)
        if (typeof __options === 'function') {
            callback = __options;
            __options = {};
        }

        _options = __options;
        _options.baseAmazonPage='amazon.co.jp'; //追加した行

        if (!email || !password) {
            __options.proxyOnly = true;
        }

        initConfig();
(略)

編集後のファイルをGitHubに置いておきます。

github.com

続いてproxy.jsファイルを編集します。

$ cd ~/.node-red/node_modules/alexa-cookie2/lib/
$ cp proxy.js proxy.js.org
$ vi proxy.js

このファイルはAlexa-Cookie2 v3.2.1版とは変数名などが変更になっています。また、修正が行われている部分もあるので、直さなくても良い部分もありました。

proxy.js編集後178行目あたりを以下の様に編集

(略)
            if (url === '/') { // initial redirect
//以下が編集場所
//以下の行をコメントアウト
                //returnedInitUrl =  `https://www.${_options.baseAmazonPage}/ap/signin?openid.return_to=https%3A%2F%
2Fwww.${_options.baseAmazonPage}%2Fap%2Fmaplanding&openid.assoc_handle=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&pageId=amzn_dp_project_dee_ios${_options.baseAmazonPageHandle}&accountStatusPolicy=P1&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns.oa2=http%3A%2F%2Fwww.${_options.baseAmazonPage}%2Fap%2Fext%2Foauth%2F2&openid.oa2.client_id=device%3A${deviceId}&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.oa2.response_type=token&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.pape.max_auth_age=0&openid.oa2.scope=device_auth_access&language=${_options.amazonPageProxyLanguage}`;
//以下の行を追加
                returnedInitUrl =  `https://www.amazon.co.jp/ap/signin?showRmrMe=1&openid.return_to=https%3A%2F%2Fal
exa.amazon.co.jp%2F&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.assoc_handle=amzn_dp_project_dee_jp&openid.mode=checkid_setup&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&`;
                _options.logger && _options.logger('Alexa-Cookie: Initial Page Request: ' + returnedInitUrl);
                return returnedInitUrl;
            }
            else {
                return `https://www.${_options.baseAmazonPage}`;
            }
        }
        return `https://alexa.${_options.baseAmazonPage}`;
    }
(略)

編集後のファイルをGitHubに置いておきます。

github.com

2つのファイルの編集が終わったら、ブラウザのCookieを一旦削除します。その後Node-RED再起動させます。Node-REDが再起動できたら フローエディターでデプロイボタンクリックします。更に、ブラウザによる認証を行います。ブラウザの認証結果はエラーがでてしまうことが多いのですが、その際も 【alexa routineノード】の下にあるメッセージがreadyと表示されていれば認証は問題はないようです。

f:id:ueponx:20200724124256p:plain

この認証に関しては【デプロイ】ボタンを押す度に認証作業が行われるので注意が必要です。 readyとなったら、ユーザの持っている出力するAlexa(Echoデバイス)のデバイスリストがプロパティとして表示されるので、更に追加で設定を行います。 デバイスと話すメッセージを設定しておきます。

f:id:ueponx:20200728021840p:plain

設定が終わったら、【デプロイ】ボタンをクリックし更に認証も行ってください。すべての作業が終わってから【injectノード】をクリックすると、Alexa から発話(音声出力)が行われます。

f:id:ueponx:20200728003931p:plain

youtu.be

これでようやくTTSができるようになりました。ただ単純にTTSを使用するというのであれば【play audioノード】を使えばNode-RED単体でもTTSは使用できます。ただ、声のわかりやすさでいうとAlexaの方が聞き取りやすいとは思います。

以下のような、hostnameコマンドを【execノード】で使用すれば、IPアドレスを話してくれます。

f:id:ueponx:20200728011802p:plain

f:id:ueponx:20200728011810p:plain

youtu.be

IPアドレスを音声出力するフロー (注)AlexaのProxy認証のIPはご自身のもので変更をお願いします

[
    {
        "id": "85e5efde.abce6",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "f35759ca.5a4e08",
        "type": "inject",
        "z": "85e5efde.abce6",
        "name": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 180,
        "y": 60,
        "wires": [
            [
                "c2709e55.ffe76"
            ]
        ]
    },
    {
        "id": "3fae9c80.5d9904",
        "type": "alexa-remote-routine",
        "z": "85e5efde.abce6",
        "name": "",
        "account": "8c87c383.65b71",
        "routineNode": {
            "type": "speak",
            "payload": {
                "type": "regular",
                "text": {
                    "type": "msg",
                    "value": "payload"
                },
                "devices": [
                    "G090L90971660A16"
                ]
            }
        },
        "x": 640,
        "y": 60,
        "wires": [
            []
        ]
    },
    {
        "id": "c2709e55.ffe76",
        "type": "exec",
        "z": "85e5efde.abce6",
        "command": "hostname -I",
        "addpay": false,
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "oldrc": false,
        "name": "IPアドレスの取得",
        "x": 410,
        "y": 60,
        "wires": [
            [
                "3fae9c80.5d9904"
            ],
            [],
            []
        ]
    },
    {
        "id": "8c87c383.65b71",
        "type": "alexa-remote-account",
        "z": "",
        "name": "Alexa",
        "authMethod": "proxy",
        "proxyOwnIp": "192.168.0.17",
        "proxyPort": "3456",
        "cookieFile": "",
        "refreshInterval": "3",
        "alexaServiceHost": "alexa.amazon.co.jp",
        "amazonPage": "amazon.co.jp",
        "acceptLanguage": "ja_JP",
        "userAgent": "",
        "useWsMqtt": "on",
        "autoInit": "on"
    }
]

また、vcgencmd measure_tempコマンドを【execノード】で使用することでCPU温度を音声出力することもできます。

f:id:ueponx:20200728013129p:plain

youtu.be

CPU温度を音声出力するフロー (注)AlexaのProxy認証のIPはご自身のもので変更をお願いします

[
    {
        "id": "85e5efde.abce6",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "f0225aaa.afda48",
        "type": "alexa-remote-routine",
        "z": "85e5efde.abce6",
        "name": "",
        "account": "8c87c383.65b71",
        "routineNode": {
            "type": "speak",
            "payload": {
                "type": "regular",
                "text": {
                    "type": "msg",
                    "value": "payload"
                },
                "devices": [
                    "G090L90971660A16"
                ]
            }
        },
        "x": 760,
        "y": 60,
        "wires": [
            []
        ]
    },
    {
        "id": "f35759ca.5a4e08",
        "type": "inject",
        "z": "85e5efde.abce6",
        "name": "",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 160,
        "y": 60,
        "wires": [
            [
                "b1ed8cb2.c8cec"
            ]
        ]
    },
    {
        "id": "b1ed8cb2.c8cec",
        "type": "exec",
        "z": "85e5efde.abce6",
        "command": "vcgencmd measure_temp",
        "addpay": false,
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "oldrc": false,
        "name": "CPU温度の取得",
        "x": 360,
        "y": 60,
        "wires": [
            [
                "14dd345b.a94cbc"
            ],
            [],
            []
        ]
    },
    {
        "id": "14dd345b.a94cbc",
        "type": "change",
        "z": "85e5efde.abce6",
        "name": "",
        "rules": [
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "temp=",
                "fromt": "str",
                "to": "現在のCPU温度は",
                "tot": "str"
            },
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "'C",
                "fromt": "str",
                "to": "度です",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 560,
        "y": 60,
        "wires": [
            [
                "f0225aaa.afda48"
            ]
        ]
    },
    {
        "id": "8c87c383.65b71",
        "type": "alexa-remote-account",
        "z": "",
        "name": "Alexa",
        "authMethod": "proxy",
        "proxyOwnIp": "192.168.0.17",
        "proxyPort": "3456",
        "cookieFile": "",
        "refreshInterval": "3",
        "alexaServiceHost": "alexa.amazon.co.jp",
        "amazonPage": "amazon.co.jp",
        "acceptLanguage": "ja_JP",
        "userAgent": "",
        "useWsMqtt": "on",
        "autoInit": "on"
    }
]

注意点があるとすれば、以下のように【Alexa Routineノード】を連続させると最後のものしか音声として出力されません。(上書きされると言った方がいいかも)

f:id:ueponx:20200728014235p:plain

そのため、連続して音声を出力させる場合には、例えば以下の様に適切なウエイトを【delayノード】を使用して作る必要があります。

f:id:ueponx:20200728014757p:plain

youtu.be

おわりに

さらっと使えるのかなと思ったらライブラリを編集しないと使えないとは…でも、とりあえずはなんとかなったかな。ただ、認証時にエラーがでるのであんまりいい修正ではないですが。参考にしたリンクの方が、proxy認証時のエラーの対応を依頼されているので、本家側で修正されることを願います。ソースを見た感じでは少し修正している印象でした。

Node-REDの【play audioノード】の声は今ひとつ(たどたどしい印象)でしたが、Alexaを使用できることによってかなり良い品質のTTSデバイスが手に入った感じです。非常に聞き取りやすいです。送信して読み上げてくれる文字数の限界は試してみたところ114字(ひらがな50音で句読点つき)といったところでしょうか。

今度は音声入力にも挑戦してみようと思います。

/* -----codeの行番号----- */