AWS IoT Enterprise ButtonとNature Remoを利用して家電を操作してみた

少し前に東芝レグザを購入したのですが、Amazon Echo で操作できるのが思いの他便利だったので、この際リビングの他の家電もEcho で操作してみようと思い立ち、Nature Remo というスマートリモコンを購入しました。

https://nature.global/

エアコンや照明、シーリングなど家電単体でコントロールは当然できますが、複数の家電を一括で操作したい場合に非常に便利なのが”シーン” 機能です。シーンを利用すると、リビングの複数の家電を一括で操作できるようになります。またこのシーン機能はAmazon Echo と連携できて、例えば、定形アクションで、”アレクサ、おはよう” という定型文とシーンを紐付けることで、朝起きてエアコンと照明とシーリングとテレビを自動でつけるなどが可能になります。
また、我が家では、見た目が気になるからと妻がすべてのリモコンをテーブルの下に隠れるように置いているのですが、毎晩寝る前に複数のリモコンを引っ張り出して来て電源OFFにするという操作が割と面倒でした。今では、”アレクサ、おやすみ” といえばリビングの家電をすべて自動でOFFにできます。

さらに、このNature Remo はインターネット上で利用できるNature Remo Cloud API が公開されているので、AWS 上のLambda などのプログラムからいろいろと操作が可能です。この点も、Nature Remo を選択した理由の一つです。

今回は、去年購入して引き出しに眠らせていたAWS IoT Enterprise Button を利用してボタン押したらリビングの家電をオフにするLambda 実装を試してみました。Enterprise という名称がついていますが、個人宅で気軽に遊び感覚で利用できます。

https://www.amazon.co.jp/gp/product/B075FPHHGG

AWS IoT Enterprise Button はAWS IoT 1-Click というサービスと連携し、ボタンを押した時に任意のLambda を非常に簡単に実行できます。今回は一通りの設定の流れをご紹介します。

Lambda の実装例

まずは、Nature Remo と連携するLambda を作成します。細かい説明は割愛しますが、実装したコードをご紹介します。ランタイムはNode.js です。IoT Button はシングルクリック、ダブルクリック、長押しの3つのクリックタイプに対応しています。この実装では、シングルクリックはスイッチオフ、ダブルクリックもしくは長押しの場合は、スイッチオンの動作とします。
残念ながら、Nature Remo Cloud APIではシーンを操作するというAPI は現時点で提供していない、この実装ではそれぞれの家電を個別に操作しています。以下の実装では、照明とエアコンを操作します。
なお、Nature Remo Cloud API の利用方法は以下のマニュアルを参照しましょう。

https://developer.nature.global/

以下、Lambda 実装例です。

const AWS = require('aws-sdk');
const https = require('https');

AWS.config.update({ region: 'ap-northeast-1' });

const encryptedToken = process.env['encryptedToken'];
let decryptedToken;

const lightOffSignalId = 'your_light_off_signal_id';
const lightOnSignalId = 'your_light_on_signal_id';

const airConOffSignalId = 'your_aircon_off_signal_id';
const airConOnSignalId = 'your_aircon_off_signal_id';


function processEvent(event, context, callback) {
    console.log('event', event);

    let clickType = event.deviceEvent.buttonClicked.clickType;
    let lightSignalId;
    let airConSignalId;

    // Switch processing by click type
    // SINGLE is off
    if (clickType === 'SINGLE') {
        console.log("Appliances off");
        lightSignalId = lightOffSignalId;
        airConSignalId = airConOffSignalId;
    } else if (clickType === 'DOUBLE' || 'LONG') {
        console.log("Appliances on");
        lightSignalId = lightOnSignalId;
        airConSignalId = airConOnSignalId;
    }
    
    // POST to Nature Remo Cloud API
    let headers = {
        'Authorization': `Bearer ${decryptedToken}`
    }
    
    // Control Light
    let lightoptions = {
      protocol: 'https:',
      host: 'api.nature.global',
      path: `/1/signals/${lightSignalId}/send`,
      method: 'POST',
     headers: headers
    };
    
    // Control Air Conditionar
    let airconoptions = {
      protocol: 'https:',
      host: 'api.nature.global',
      path: `/1/signals/${airConSignalId}/send`,
      method: 'POST',
     headers: headers
    };
    
    postRequest(lightoptions).then(statusCode => {
        if (statusCode != '200') {
            throw `Light Control Error: StatusCode is  ${statusCode}`;
        } else {
            console.log("Light Controll Succeeded!");
            return postRequest(airconoptions);
        }
    }).then(statusCode => {
        if (statusCode != '200') {
            throw `AirCon Control Error: StatusCode is  ${statusCode}`;
        } else {
            console.log("AirCon Controll Succeeded!");
            callback(null, 'ok');
        }
    }).catch(err => {
        console.error(`Error: ${err.message}`);
        callback(err);
    });
}

async function postRequest(opsions) {
    return new Promise((resolve, reject) => {
        const req = https.request(opsions, (res) => {
            console.log('Status:', res.statusCode);
            resolve(res.statusCode);
        }).on('error', (err) => {
          console.log('error:', err);
          reject(err);
        });
        req.end();
    });
}

exports.handler = (event, context, callback) => {
    if (decryptedToken) {
        processEvent(event, context, callback);
    } else {
        // Decrypt code should run once and variables stored outside of the
        // function handler so that these are decrypted once per container
        const kms = new AWS.KMS();
        kms.decrypt({ CiphertextBlob: new Buffer(encryptedToken, 'base64') }, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                return callback(err);
            }
            decryptedToken = data.Plaintext.toString('ascii');
            processEvent(event, context, callback);
        });
    }
};

Nature Remo Cloud API 呼び出し時のToken は慎重に取り扱ってください。上記実装では、Lambdaの環境変数に暗号化した上で保存しています。もしTokenが漏れていしまうと、第三者が自宅の家電を自由に操作できてしまいますので、平文でコード中にトークンをそのまま書くということはやめましょう。

コードの中で利用しているSignalID は、Nature Remo に登録したアプライアンス(家電)のボタンごとに割り当てられるID です。このSignalID にPOST リクエストを送信することで、家電を操作できます。SignalID は、以下のようなコマンドで確認できます。

curl -X GET "https://api.nature.global/1/appliances" -H "Authorization: Bearer <your_token>"

AWS IoT Enterprise Button の設定

スマホで、”AWS IoT 1-Click” アプリをダウンロードします。初回起動時にAWS アカウントのログインが必要となります。(ルートユーザではなく、IAM ユーザでログインしましょう)

アプリを起動したら、デバイスを登録します。アプリのセットアップ画面にて、デバイスを登録します。なお、Enterprise Button 専用のWi−Fi 設定項目がありますが、デバイス登録時に合わせて設定できますので、”デバイスID で登録”からIoT Button を登録します。デバイスが登録されたら、”有効化”しておきます。

AWS IoT 1-Click の構成

マネージメントコンソールにログインして、”AWS IoT 1-Click” サービス画面を表示します。以下のようにデバイスが登録されていることが確認できます。デバイスを有効にしていない場合は、”有効” にしておきます。

AWS IoT 1-Click では、最初にプロジェクトの作成をします。

プロジェクトの中で、デバイステンプレートを作成します。その際に先ほど作成したLambda を設定します。

ここでは、デバイスの属性も設定できます。今回の処理では特に利用しませんが、家の各部屋にボタンを配置するような場合に各デバイスごとに属性値を指定できます。


プロジェクトを作成したら、次はプレイスメントを作成し、登録したIoT Buttonを関連付けます。以下のような感じで、リビング用のプレイスメントを作成しました。

構成方法はこれだけです。後は実際にボタンを押すとLambda が起動しますので、正常に起動できたかどうかを確認してください。

こんな感じで、Nature Remo を利用すると、AWS IoT Button から非常に簡単に家電を操作できます。あまりの手軽さに、こんなにIoT の世界が身近に来たのかと感動しました!さらにアイディア次第でいろいろな使い方が出てきそうですね!

以上です。