AWS IoTボタンで電気を消す

リビング照明にはリモコンがついていて、Nature Remoでつけたり消したりしているんですが(実際はアレクサがやってくれている)、寝るときは壁スイッチで消すことが多いです。そっちのほうが動線的に自然だし。

ただ、これだと主電源が切られたようなもんで、もう一回その壁スイッチを押して主電源をOnにしないとリビング照明がつきません。「アレクサ、電気つけて」といってNature Remoがピッて言っても、電気がつきません。

ということで、壁スイッチの横にAWSボタンを置いて、壁スイッチを押さずにAWSボタンを押すことで電気を消すようにしました。以下の図の、右下のボタンです。(左上のSwitchbotのボタンは今回は関係ないです)

ちなみに、これ、正式名称は「AWS IoT Enterprise Button」というみたいですが、2022/5月時点で販売中止になってました。安くてよかったのに。。代替品はめちゃ高いので買う気がしません。。。

https://aws.amazon.com/jp/iot-1-click/devices/

なので、ボタンはこの1個だけで、今後の増設はNFCタグで行います。詳しくは以下で!

仕組み = ボタン → Lambda → Nature Remo → 電気消す

ボタンを押したら、Lambdaが動いてNatureRemoに指示を出し、NatureRemoがその指示に従って電気を消す という流れです。

以前はIFTTTを使っていましたが、IFTTTが有料になったため、IFTTTを使わずにLambdaだけで行います。

Lambdaとは、AWSが提供しているサービスの一つで、自分でPythonなどのプログラムを書いて、SwitchbotやNature Remoなどを動かすことができます。ちなみに、AWSのサービスは有料で、クレカの登録が必要になります。が、今回のような「単にボタンを押したらRemoを動かす」だけだったら、月に100円程度です♪

事前準備

AWS IoTボタンのセットアップ

スマホを使って登録します。AWS IoT 1-Clickというアプリをスマホにインストールしてください。登録までの流れはアプリがわかりやすくリードしてくれます。

ちなみに登録にはDSN番号なるものが必要です。ボタンの背面に記載されているので、ボタンを壁に貼り付ける前にメモっておく必要があります!
(箱にも書かれているので、箱があれば大丈夫ですが)

AWSアカウントの作成

まずはAWSアカウントを作りましょう。利用料金がかかるしクレジットカードの登録も必要ですが、臆さず登録しましょう!

https://aws.amazon.com/jp/register-flow/

すさまじくいろいろなサービスがありますが、このなかの「Lambda(ラムダ)」を使います。

Nature RemoのTOKENを取得

以下のサイトへ行って、TOKENを発行しましょう。画面に一度そのTOKENが出てくるだけで、あとから再表示はできないようなので、しっかりTOKENをメモっておきましょう。

https://home.nature.global/

僕は以下のサイトを参考にしました。
https://blog-and-destroy.com/12297

これで準備は完了です!次はいよいよLambdaでプログラムを書いて、自分のNature Remoとやり取りします。

Nature Remoに登録されている家電IDを調べる

Lambda関数の新規作成(空)

AWSで作業をします。AWSコンソールに行ったら、まずはリージョンのチェック!画面の右上でリージョンを変更できます。「東京」になっていればOK。「バージニア北部」とかになっていれば、「東京」に変更してください。これ、すごく重要です!

次に、画面上部にサービスの検索窓があるので、そこに「Lambda」と入れて、出てきたサービス欄からLambdaをクリック。

右上の「関数の作成」をクリック。
  関数名 = 「NatureRemo_GetDevieID」
  ランタイム = 「Python3.9」
(2022/5時点。その時の最新のを選べばOKです)
と入れて、あとはデフォルト設定のままで、右下の「関数の作成」ボタンをクリック。

以下のブログが参考になりました。
https://zenn.dev/nakam_aws/articles/46fcc003855aef

環境変数の設定(TOKEN)

環境変数へ、TOKEN情報を書き込みます。(プログラム内に直書きでもOKですが、環境変数に書いたほうがすっきりします)

「設定」タブ→「環境変数」→「編集」と押し、

「環境変数の追加」で、キー欄に「TOKEN」と、値欄に先ほどメモしたRemoのTOKEN番号を貼り付けましょう。
  キー 「TOKEN」
  値  RemoのTOKEN番号

入力したら、保存してください。

Remoの家電ID(ApplianceID)を取得するプログラムを書く①

まずは、以下のコードを貼り付けてください。ちなみに、はまったところは、「認証が通らない」「Requestsライブラリが使えない」といった点でした。

import json
import os
import urllib.request

def lambda_handler(event, context):

    #--- TOKEN ---------------#
    TOKEN = os.environ.get('TOKEN')

    #--- ヘッダ --------------#
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + TOKEN,
    }

    #-- 家電ID取得用のURL --#
    url = 'https://api.nature.global/1/appliances'

    #--- 取得 
    req = urllib.request.Request(url=url,headers=headers, method='GET')

    #--- 取得 ----------------#
    res = urllib.request.urlopen(req)
    jjj = json.loads(res.read())

    #--- ログに出力(全部)---#
    print('-----------------')
    print(json.dumps( jjj, indent=4, ensure_ascii=False ))
    print('-----------------')

このコードを貼り付けたら、上のほうにある「Deploy」ボタンを押してください。これでプログラムが実行できる状態になりました。

Deployボタンを押した後、テストタブを開いて

テストタブ

右側にあるオレンジの「テスト」ボタンを押してください。

以下のような画面がでて、テストが成功したら、もう一回「コード」タブに戻ってください。

成功したら、このような緑の枠組みが出てきます。

「Execution Result」が表示されていると思います。そこにログが出力されています。

ただ、ログがぐちゃっとなって見づらいと思います。

Remoの家電ID(ApplianceID)を取得するプログラムを書く②

ID/名前/ボタン情報 だけを出力するようにします。上記のコードの最後に以下のプログラムを追加してください。

    #--- ログに出力(IDだけ)---#
    for j in jjj:
        print('-----------------')
        print('id=' + j['id'])
        print('name=' + j['nickname'])
        print('signal=' + json.dumps(j['signals'], ensure_ascii=False))

Deployを忘れずに!Deployしたら、テストタブに行って、テスト実行して、コードに戻って、ログを見てみてください。

以下のようなのがログに出てきていると思います。idが「家電ID」で、ついでに後で必要になるシグナルIDも取得できました。

Remoに登録されているリビング照明の家電IDは「xxxxx」で、照明をつけるオンボタンのシグナルIDは「yyyyy」、オフボタンのシグナルIDは「zzzzz」という感じです。

-----------------
id=xxxxx
name=電気
signal=[{"id": "yyyyy", "name": "オン", "image": "ico_on"}, {"id": "zzzzz", "name": "オフ", "image": "ico_off"}]
-----------------

ちなみに、シグナルIDに何も出ていないことがあります。これは、リモコンのボタン1つひとつを登録するのではなく、リモコンをマルっと登録したものになります。TVとかエアコンとかに多く起こります。この場合、ちょっとやり方が変わります(後述)。

※ シグナルに何も出てこないパターン
-----------------
id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
name=テレビ
signal=[]
-----------------

LambdaでRemoを動かす!

シグナルIDがあるケース

うちのRemoに登録されている家電「電気」は、シグナルIDがあったので、このケースです。

新たにLambda関数を新規作成しましょう。名前は、「NatureRemo_LightOn」とか。
そして、環境変数にTOKEN情報を書き込んでください。

そしたら、デフォルトで入っているコードを消して、以下のコードを貼り付けてください。
また、「yyyyy」の部分(url_lightのところ)を、上記で取得したシグナルIDに書き換えてください。

import json
import os
import urllib.request

def lambda_handler(event, context):

    #--- TOKEN ---------------#
    TOKEN = os.environ.get('TOKEN')

    #--- ヘッダ --------------#
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + TOKEN,
    }

    #--- 電気用のURL    ------#
    url_light = 'https://api.nature.global/1/signals/yyyyy/send' #←要修正!yyyyy

    #--- 電気を消す -------------------------#
    req = urllib.request.Request(url=url_light, headers=headers, method='POST')
    urllib.request.urlopen(req)

そうです。家電IDは使いません♪ シグナルIDだけで動きます!

Deployし、テストタブに行き、テストを実行してください。これで電気がつけば、成功です!

僕は、初めて成功したときは結構感動しました^^

シグナルIDがないケース

TVやエアコンなど、リモコンをマルっと登録した家電にはシグナルIDがつきません。そんな時は、以下のような感じで動かします。

関数を新規作成し、環境変数にTOKEN情報を入れ、コード欄に以下のコードを貼り付けてください。

こちらのケースでは、家電IDを使うことになります。
urlのところにあるxxxxx・・・を、家電IDに書き換えてください。

import json
import os
import urllib.request


def lambda_handler(event, context):

    TOKEN = os.environ.get('TOKEN')
    url = 'https://api.nature.global/1/appliances/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/tv'

    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + TOKEN,
    }

    data = b'button=power'

    req = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
    urllib.request.urlopen(req)

ポイントは、「data = b’button=power’」のところ。”power”が、電源ボタンを指します。すなわち、これを動かすと、TVリモコンの電源ボタンを押した動きになります。

このように、どのボタンを押すかをデータで渡す必要があるのですが、その渡し方にはまった。。。
以下のブログに解決策が書かれており、非常に助かりました。
https://qiita.com/souring001/items/68b5397c1750c325f61a

TVボタン名の調べ方

ボタン名(name)を調べるには、以下のコードを新規作成して回してみてください(TOKEN情報を環境変数に入れるのを忘れずに)。

import json
import os
import urllib.request

def lambda_handler(event, context):

    #--- TOKEN ---------------#
    TOKEN = os.environ.get('TOKEN')

    #--- ヘッダ --------------#
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + TOKEN,
    }

    #-- DeviceID取得用のURL --#
    url = 'https://api.nature.global/1/appliances'

    req = urllib.request.Request(url=url,headers=headers, method='GET')

    #--- 取得 ----------------#
    res = urllib.request.urlopen(req)
    jjj = json.loads(res.read())

    #--- ログに出力(IDだけ)---#
    for j in jjj:
        print('-----------------')
        print('id=' + j['id'])
        print('name=' + j['nickname'])
        print('signal=' + json.dumps(j['signals'], ensure_ascii=False))
        if('tv' in j.keys()):
            print('TV=' + json.dumps(j['tv']['buttons'], ensure_ascii=False))
        if('aircon' in j.keys()):
            print('aircon=' + json.dumps(j['aircon'], ensure_ascii=False))

ログに、以下のような情報が出ているはずです。ここから、どのボタンがどんな名前か勘を働かせて特定します。

TVのボタン名一覧

これで、「Lambda→Remo」の部分が組みあがりました!一番面倒なLambdaはこれでおしまいです。

あともう少しです!「ボタン→Lambda」の部分を組み込めば完成です!!

ボタンを押したらLambdaを動かす

こちらは、ボタンを登録したときに使ったAWS IoT 1-Clickスマホアプリを使います。

「プロジェクト」タブから新規作成し、「デバイステンプレート」で、先ほど作ったLambda関数(NatureRemo_LightOn)を選択し、「プレイスメント」で冒頭に登録したIoTボタンを指定してあげるといった流れです。

僕はスマホアプリでトラブルなく作成できましたが、他の人はWebのAWSサービス(IoT 1-Click)で設定されてる人が多いみたいです。
https://qiita.com/hayao_k/items/d36d4b2579d0bbbe21a9
などがとても分かりやすかったです。

これで完成!ボタンをぽちっとして、ちゃんと電気が消えるか試してみましょう♪

おまけ:実際に使っているコード

実際には、電気を消すと同時にTVも消したいですよね。また、このAWS IoTボタンは、ダブルクリックなどにも対応してます。なので、ダブルクリックしたときは、電気をつけるようにしています。

テレビのボタンって、「On/Offボタン」が独立してそれぞれあるわけじゃなく、「電源ボタン」がひとつしかない。。でも、「電源がついてなければ電源ボタン押すのスルーして」なんてことはできない。なので、ちょっとした工夫が必要です。

いったん「入力切替」ボタンを押しています。もしテレビが消えていたら、これでいったんテレビがOnになります。もしテレビがついていたら、ついたままです。その後、電源ボタンを押しています。電源ボタンを押す前に「必ずついてる状態」にしている感じです。

加えて、NFCタグからも実行させているので、その部分も書き加えています。

なお、環境設定に、TOKEN / 照明OnのボタンシグナルID / 照明OffボタンのシグナルIDの3つを登録しています。

import json
import os
import urllib.request
import time

def lambda_handler(event, context):

    #--- ID情報を環境設定から取得 ---#
    ID_TV = os.environ.get('ID_TV')
    ID_LIGHTOFF = os.environ.get('ID_LIGHTOFF')
    ID_LIGHTON  = os.environ.get('ID_LIGHTON')

    #--- TOKEN ---------------#
    TOKEN = os.environ.get('TOKEN')

    #--- ヘッダ --------------#
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': 'Bearer ' + TOKEN,
    }

    #--- AWSボタンとNFCタグの両方からくる-> NFCタグから来たら、シングルクリックにする
    if(json.dumps(event).find('deviceEvent') == -1):
        clicktype = 'SINGLE'
    else:
        clicktype = event['deviceEvent']['buttonClicked']['clickType']
        

    #----------------------------------------------------------------#
    #--- ダブルクリックなら電気をつけて、それ以外は電気を消す -------#
    #----------------------------------------------------------------#

    #--- 電気用のURL    ------#
    if (clicktype == "SINGLE"):
        url_light = 'https://api.nature.global/1/signals/' + ID_LIGHTOFF + '/send'
    elif (clicktype == "DOUBLE"):
        url_light = 'https://api.nature.global/1/signals/' + ID_LIGHTON  + '/send'
    elif (clicktype == "LONG"):
        #ボタン長押し
        url_light = 'https://api.nature.global/1/signals/' + ID_LIGHTOFF + '/send'
    else:
        url_light = 'https://api.nature.global/1/signals/' + ID_LIGHTOFF + '/send'

    #--- 電気を消す or つける -------------------------#
    req = urllib.request.Request(url=url_light, headers=headers, method='POST')
    urllib.request.urlopen(req)




    #--- TV用のURL  ---------#
    url_TV = 'https://api.nature.global/1/appliances/' + ID_TV + '/tv'

    #----------------------------------------------------#
    #--- ダブルクリックじゃなければ、TVを消す   ---------#
    #----------------------------------------------------#
    #いったんつける
    if (clicktype == "SINGLE"):
        data = b'button=select-input-src' #電源ボタンじゃないのがミソ
        req = urllib.request.Request(url=url_TV, data=data, headers=headers, method='POST')
        urllib.request.urlopen(req)

        time.sleep(5) #ちょっと待つ

        #--- 消す
        data = b'button=power'
        req = urllib.request.Request(url=url_TV, data=data, headers=headers, method='POST')
        urllib.request.urlopen(req)

以上、参考になれば幸いです。ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA