Alexaによる野球チャンネルの自動化3(Nature Remo Local API)

前回はNatutre RemoのクラウドAPIを使用してテレビのリモコン信号を制御しましたが、この方法だとNature Remoのクラウドサーバーを経由することになり、5分間30回のリクエスト回数の制限に引っかかりやすい、Nature Remoのサーバーが落ちていると使えない、という問題がありました。これを回避するために、Local APIを使用して、サーバーを経由せず直接Nature Remoに通信してリモコン信号を出す方法を探りました。
※方法など前回と重複するものは記載しません。

Nature Remoのアドレスを固定する

Nature Remoに対して直接通信するわけですが、Remoは今までwifiルーターのDHCPによってIPアドレスが割り当てられていました。このまま状態でプログラムを作ると使用開始前にRemoのIPアドレスを調べて、それに対して通信を行うという面倒なものになりそうなので、まずアドレスを固定しました。
他のwifiルーターにも同じような機能はあると思うのですが、私の持っているwifiルーターBuffalo Air Station WSR-1166DHPL2(古い安物)では、DHCPにMACアドレスに対して固定のIPアドレスを割り当てるという機能がありましたので、それを使用します。

まず、RemoのMACアドレスを調べます。
Test3.py
from remo import NatureRemoAPI
api = NatureRemoAPI(‘XXXアクセストークンXXX’)
devices = api.get_devices()
print(devices)

実行結果にmac_address=’xx:xx:xx:xx:xx:xx’とRemoのMACアドレスがあるので、それをもとにアドレスを固定化します。JSON整形ツールで整形するとMACアドレスを探しやすいです。
wifiルータにログインし、[詳細設定]-[LAN]-[DHCPリース]画面を開き、該当のMACアドレスに対して固定のIPアドレスが割り当てられるように設定します。wifiルーターの再起動で反映されます。

Nature Remo Local APIのページ(https://local-swagger.nature.global/)にもありますし、それを説明したサイトにもありますが、dns-sdコマンドを使っています。これについて調べてみました。

dns-sdコマンドはmDNS(マルチキャストDNS)に関係しており、既にmDNSはWindows10 に搭載している、ということまではわかりましたが、dns-sdコマンドをコマンドプロンプトで打ってもそんなコマンドがないという感じです。
探した結果、以下のページが参考になりました。

networking – dns-sdコマンドラインテストツールをWindowsまたはLinuxにインストールする方法
https://tutorialmore.com/questions-1800406.htm

dns-sdはアップルのサイトでBonjour SDKをインストールすると使えるようになるとのことでした。以下のサイトにアクセスし、[Bonjour SDK for Windows]をクリックし、ダウンロードします。Apple IDの入力を求められます。アカウントがない場合は作成します。
https://developer.apple.com/bonjour/

Bonjour SDKをインストールするとdns-sdコマンドがコマンドプロンプト使えるようになりました。

>dns-sd -B _remo._tcp

しかし、いつまで待っても何の応答もありません。Ctrl+Cで強制終了しました。
いろいろ調べてみましたが、wifiルーターの以下の設定が気になりました。
[詳細設定]-[無線設定]-[マルチキャスト制御]画面のSnooping機能が「使用する」になっていました。これはネットワークの余計なトラフィックを減少させるため、セキュリティのためにある機能のようです。理解したわけではありませんが、これを一時的に解除し、試してみました。終わったら使用する、に戻しました。

> dns-sd -B _remo._tcp
Browsing for _remo._tcp
Timestamp     A/R Flags if Domain                    Service Type              Instance Name
11:20:11.476  Add     2 24 local.                    _remo._tcp.               Remo-XXXXXX

Nature Remoが見つかったようです。このあと終わらないのでCtrl+Cで強制終了しました。得られた名前に対して.localを付け加えて以下のコマンドも試しました。

> dns-sd -G v4 Remo-XXXXXX.local
Timestamp     A/R Flags if Hostname                  Address                                      TTL
11:23:47.264  Add     2 24 Remo-XXXXXX.local.        192.168.xx.xx                                120

固定で割り当てたアドレスが取得できました。dns-sdなるものをネットで調べるのにかなり時間がかかってしまいましたので、得られた回答に少し拍子抜けしました。要は名前をマルチキャストすると該当するデバイスが返事を返す仕組みになっていて、それを使ってIPアドレスを取得しろ、ということだったのかな。Bonjour SDKはこれ以上必要ないのでアンインストールしました。

Nature Remo Local APIについて

Local APIにはGET /messageとPOST /message2つのインターフェースしかありません。GETは引数無し、POSTはメッセージのみです。この2つで何をどうすれというのでしょうか?
以下のページを参照にしました。

Nature RemoのローカルAPIを叩いて家電を操作する
https://takagi.blog/controlling-home-appliances-using-local-api-with-nature-remo/

どういうことかというと、まずリモコンをRemoに向けて、あるボタンを押します。Remoはリモコンの信号を受信すると青く点滅し、その赤外線信号のデータを一定時間記憶します。
GETするとRemoは記憶している赤外線の信号データをメッセージとして返信します。返信されたメッセージをそのボタンの信号としてメモしておきます。
メモした赤外線信号データをPOSTすると、Remoから赤外線信号を送出する、ということのようです。
どうやらRemo本体にはテレビや照明、エアコンの型式別のデータは持っておらず、リモコンのボタン1つ分のデータしか保持していないようです。

コマンドプロンプトで以下のコマンドを発行し、GETコマンドを送信します。192.168.xx.xxはRemoのIPアドレスです。

>curl -X GET http://192.168.xx.xx/messages -H “X-Requested-With: curl” -H “Expect:”

レスポンスとして以下のようなデータが返されます。
{“format”:”us”,”freq”:36,”data”:[3401,1821,353,516,……中略…… ,362,1380,351]}
レスポンスとして表示されたデータをボタンと対応させてメモしておきます。これを必要なボタンの種類だけ行います。
同じボタンでも押したときによってデータの値が微妙に違います。データの数値の意味はわかりませんが、Remoの赤外線信号の計測データなのでしょう。
※freq:36とあるのは36Hz=27.8ms間隔?、dataは27.8ms毎の信号レベル?どうでもいいことかもしれないが若干気になります。

Pythonのnature remoライブラリget_ir_signal関数を使っても赤外線信号データを取得できますが、出力がライブラリ独自のものに整形されてしまっているので、そのままでは使えませんでした。このため、curlコマンドを使用しました。curlコマンドはWindows10で標準になったようです。
送信は簡単なのでnature remoライブラリ関数send_ir_signal()を使用します。

Local API版プログラム

上記を踏まえ、今日の野球チャンネルに切り替えるプログラムを以下のように改修しました。
remo_ip=にはRemoのIPアドレスに置き換えてください。
xx-button=の部分はcurlコマンドで取得した赤外線信号のデータに置き換えてください。
プログラムをftpで転送し、上書きます。

BaseballCh.py
import pandas as pd
import openpyxl
import datetime
from remo import NatureRemoLocalAPI

excel_file = './FightersCh.xlsx'
remo_ip = '192.168.xx.xx'
bs_button = '---bs_button_data---'
cs_button = '---cs_button_data---'
down_button = '---down_button_data---'
ok_button = '---ok_button_data---'
s_button = '---s_button_data---'
ch1_button = '---ch1_button_data---'
ch2_button = '---ch2_button_data---'
ch3_button = '---ch3_button_data---'
ch4_button = '---ch4_button_data---'
ch5_button = '---ch5_button_data---'
ch6_button = '---ch6_button_data---'
ch7_button = '---ch7_button_data---'
ch8_button = '---ch8_button_data---'
ch9_button = '---ch9_button_data---'
ch0_button = '---ch10_button_data---'

# 日付の取得
dt = datetime.datetime.today()
month = dt.month
day = dt.day

# 今日のチャンネルの取得
openpyxl.reader.excel.warnings.simplefilter('ignore')
df = pd.read_excel(excel_file, sheet_name=str(month))
ch = df.iloc[day-1, 3]
if ch == ch:
    # チャンネルあり、チャンネル名の操作取得
    df_ch = pd.read_excel(excel_file, sheet_name=str('channel'))
    for row in range(len(df_ch)):
        if ch == df_ch.iloc[row, 1]:
            # chの操作数取得
            num = df_ch.iloc[row, 2]
            # Nature Remoオブジェクトの取得
            api = NatureRemoLocalAPI(remo_ip)
            # 操作数でループ
            for i in range(num):
                # ボタン操作を送信
                button = str(df_ch.iloc[row, 3+i]).upper()
                if button == 'BS':
                    api.send_ir_signal(bs_button)
                elif button == 'CS':
                    api.send_ir_signal(cs_button)
                elif button == 'S':
                    api.send_ir_signal(s_button)
                elif button == 'DOWN':
                    api.send_ir_signal(down_button)
                elif button == 'OK':
                    api.send_ir_signal(ok_button)
                elif button == '0':
                    api.send_ir_signal(ch0_button)
                elif button == '1':
                    api.send_ir_signal(ch1_button)
                elif button == '2':
                    api.send_ir_signal(ch2_button)
                elif button == '3':
                    api.send_ir_signal(ch3_button)
                elif button == '4':
                    api.send_ir_signal(ch4_button)
                elif button == '5':
                    api.send_ir_signal(ch5_button)
                elif button == '6':
                    api.send_ir_signal(ch6_button)
                elif button == '7':
                    api.send_ir_signal(ch7_button)
                elif button == '8':
                    api.send_ir_signal(ch8_button)
                elif button == '9':
                    api.send_ir_signal(ch9_button)
            break

課題

テストしてわかったのですが、5分間30回というリクエスト回数制限はLocal APIを使っても変わりませんでした。何故かそう思いこんでいたようです。
サーバーを経由してないため、Nature Remoサーバーが落ちていても動作できるのは、多分そうだと思いますが、試していないのでわかりません。
同じボタンなのに、試行毎にちょっとずつ違うデータが取得されるというのも少し気になります。(後日、数字の9をテレビが認識してくれないのが発覚し、データを再設定しました。)