東芝のテレビをHomeKitから使う方法を検討しました。そしてWeb APIと赤外線リモコン経由でテレビジョンアクセサリとして登録しました。HomeKit標準のテレビインタフェースがどのようなものか試します。
追記:最初の投稿では、Netflixボタンを使ってトグル操作問題に対応してました。これを、Web APIと赤外線のハイブリッド方式で解決することにし、記事を書き換えました。(2023/03/13)
赤外線リモコンパターンを調査
対象とするテレビの機種・型番は東芝Regzaの43G20Xです。まずはRegzaのリモコンの赤外線パターンを取得してみました。こちらの記事
で、Raspberry Pi 4に取り付けた赤外線受信モジュールがまだ機能してましたので、プログラムでon/offのパターンをm秒単位で取得しました。電源ボタンのパターンは以下でした。
[9004, 4512, 553, 588, 550, 612, 525, 586, 551, 587, 553, 587, 550, 589, 551, 1720, 551, 587, 551, 1722, 551, 1721, 551, 1722, 552, 1721, 552, 1721, 551, 1720, 553, 588, 550, 1720, 553, 587, 549, 1721, 552, 589, 549, 587, 551, 1719, 553, 587, 550, 590, 548, 588, 549, 1721, 551, 587, 551, 1721, 551, 1721, 550, 590, 549, 1719, 552, 1721, 551, 1720, 554, 39892]
グラフにするとこんな様子です。ほぼ家電製品協会のフォーマットのようです。
これをデコードすると、
0x40BF12ED
という数値です。最後の1バイトはチェックサムかと思ったのですが、ちょっと不明です。他のパターンも調べると、データの合計の256の剰余がいつも0xFEになります。
最後の1バイトはチェックサムらしいものとすると、その前の3バイトがコマンドと思われます。
0x40BF12
Regzaのコードを調べた人がいるのではと思い、この16進数で検索したところ、Web APIのコマンドを調べてくださったサイトがいくつか見つかりました。例えばこちらで、Web APIコマンド一覧が調査されてます。
他のリモコンパターンも、このWeb APIコマンド一覧に合致してました。
Web APIを調査
だったらWeb APIを使えば、赤外線信号を使わずに済むのではと期待しました。そこで試してみます。結果として、
http://[user]:[pass]@192.168.xxx.xxx/remote/remote.htm?key=40BF12
というページにSafariからアクセスすると、テレビの電源がoffになりました。192.168.xxx.xxxはテレビのIPアドレスで、[user]と[pass]には、テレビで設定するユーザ名とパスワードを使います。ユーザ名がhoge、パスワードがHOGEなら、hoge:HOGEと書きます。
一方、curlコマンドならば、
curl --digest -u [user]:[pass] 'http://192.168.xxx.xxx/remote/remote.htm?key=40BF12'
で、同じくテレビの電源がoffしました。
いずれの方法も、操作に成功すると’0’というASCII文字が返ってきます。操作に失敗すると’4’などの文字が返ってきます。
Wake On Lanに未対応機種だった
ただし、残念なことにWeb APIからは、電源がoffの状態のテレビをonにすることはできませんでした。
Safariのアクセスやcurlコマンドは、前述のように成功すると’0’が返ってきます。ところが、テレビの電源がoffの時に操作すると、操作できなくて’4’が返ってきたりします。またテレビの電源をoffにして2~3分するとweb機能もoffになるようで、そうなるとhttpの応答自体が返ってこなくなります。
Regzaのモデルによっては、Wake On Lan機能が効いて、電源offの状態からWOLでonにすることができるそうです。しかし手元のモデルは、WiFi経由でも有線LAN経由でもWOLできませんでした。他の機器にWOLのマジックパケットを送ることはできても、自分自身がWOLで起動することは出来ないようです。
ネット上にあるRegzaのマニュアルを見ると、WOLを有効にするメニューがあるようです。しかし、手元のテレビには現れませんでした。
探したところ、RegzaのWeb APIを使うスマホ用リモコンアプリ、RZハイブリッドリモが無料で配布されていました。
これを利用するとテレビのリモコンが可能なのですが、
電源が切れてしまったテレビは操作できません。
スマホアプリでテレビの電源を切ってしまうと、赤外線リモコンを使ってonにする必要がありました。なので、このアプリはほとんど使ってませんでした。
赤外線とWeb APIの比較
ここまで赤外線リモコンとWeb APIを試して、それぞれ特徴があることがわかりました。まとめると、
- 赤外線はテレビの電源が入っていなくてもテレビをonにできる
- Web APIはテレビの電源が入っていない場合は使えない(WOLが効かない機種だった。電源がoffの場合Web APIの応答が返ってこない)
- 赤外線は操作に成功したかどうかのフィードバックが無い
- Web APIは成功すると’0’が返ってくる(失敗すると’1’や’2’が帰ってくる)
です。これを見ると、テレビの電源がoffでも使える赤外線の方が有用なことがわかります。一方で、Web APIは、コマンド実行のフィードバックが得られるので、テレビのon/off状態を判定することに使えそうです。
電源ボタンがトグルな問題
赤外線もWeb APIも動作の仕様が自動化に向いていません。特に電源ボタンが問題です。このテレビリモコンの電源ボタンはトグル動作で、Web APIもそれを受け継いでいてトグル動作です。なので、現状を反転させることができますが、確実にon/offすることはできません。操作側で履歴を保持していればon/off状態を把握できなくも無いですが、例えば人が実際のリモコンでon/off操作をしてしまうと、実際と一致しなくなってしまいます。
リモコンによっては、隠しコマンドでon/offに対応した信号が用意されていることもあります。しかしRegzaには、そのようなコマンドは存在しませんでした。
ただ前述のように、電源が投入されていない場合、Web APIの問い合わせがエラーになります。これを利用して現在のon/off状態を判断することが可能です。ということで、赤外線とWeb APIの両方を使用することで解決したいと思います。
Web APIと赤外線の併用
全体構成
と言うことで、テレビの操作には、Web APIと赤外線を併用することにしました。全体の構成を以下に示します。
Raspberry Pi 4上のHomebridgeとMqttthingプラグインを使って、テレビジョンアクセサリを作ります。これがHomeKit上に表示されます。HomeKitの上でテレビジョンアクセサリが操作されると、その結果がMQTTメッセージに流れます。また、Raspberry Pi 4上にPythonプログラムを作り常駐させます。このプログラムは、HomeKitからのMQTTメッセージに対応してテレビを操作します。操作の系統は、curlコマンドを使ったWeb API経由と、MQTTで動く赤外リモコンユニットを使った赤外線経由の2系統です。
Web APIと赤外線の使い分け
Pythonプログラムは、基本的にはWeb APIでテレビ操作をします。リモコンと同じ操作がネットワークから可能で、レビの電源もoffにできます。ただし、テレビの電源が入っていない場合は(この機種にはWOL機能が無いので)Web APIでは操作できません。なので、テレビをonにする場合のみ、赤外線を使います。
Web APIの電源on/offコマンドは、テレビの電源を確実にoffにするコマンドだと考えることもできます。テレビをoffにしたい場合は、とりあえずon/offコマンドを送っておけば実現できます。つまり、
- テレビがonの場合は、Web APIコマンドでoffになり、
- テレビがoffの場合は、Web APIが効かずそのままなのでこれもoffになります。
一方、テレビをonにしたい場合はWeb APIのon/offコマンドに引き続いて、赤外線のon/offコマンドを流しておきます。そうすれば、テレビの初期状態がonでもoffでも、必ずonにすることができます。つまり、
- テレビがonの場合は、Web APIコマンドでoffになり、赤外線コマンドでonになります、
- テレビがoffの場合は、Web APIコマンドは効かず、赤外線コマンドでonになります。
Web APIと赤外線は以下のように実装することとしました。
Web API操作
Web APIは、curlコマンドを使うことにしました。前述のように、以下のコマンド
curl --digest -u [user]:[pass] 'http://192.168.xxx.xxx/remote/remote.htm?key=40BF12'
で、テレビの電源がoffします。onにすることはできません。このシェルコマンドを、Pythonから呼び出すことにしました。その部分を以下のように実装して、callRGZ()という関数を作りました。引数にASCII文字列で、b’power’, b’1′, b’2’などを指定すると、それぞれ電源ボタン、チャンネル1、チャンネル2のボタンに対応するコードをWeb APIに送ります。
#!/usr/bin/python
import subprocess
webcode={
b'power':'40BF12',b'1':'40BF01',b'2':'40BF02',b'3':'40BF03',b'4':'40BF04',b'5':'40BF05',b'6':'40BF06',b'7':'40BF07',b'8':'40BF08',b'9':'40BF09',b'10':'40BF0A',b'11':'40BF0B',b'12':'40BF0C'
}
# call Regza Web API
# btnname = name on the remote button
# returns True or False (success or fail)
def callRGZ(btnname):
rgzURL = 'http://192.168.xxx.xxx/remote/remote.htm?key=' + webcode[btnname]
curl = ['curl','--digest','-u','[user]:[pass]',rgzURL,'--connect-timeout','1']
cp = subprocess.run(curl,capture_output=True)
return(cp.stdout == b'0')
Pythonのsubprocessクラスを用いてシェルコマンドを実行します。subprocessクラスのrun()メソッドを使うと、シェルコマンドを実行できます。run()メソッドの引数は、一単語ならば文字列で良いですが、複数のオプションを使う場合は、文字列のリストを使用します。なので、
curl --digest -u [user]:[pass] 'http://192.168.xxx.xxx/remote/remote.htm?key=40BF12' --connect-timeout 1
というシェルコマンドは、
subprocess.run(['curl','--digest','-u','[user]:[pass]','http://192.168.xxx.xxx/remote/remote.htm?key=40BF12','--connect-timeout','1'])
とすれば実行できます。(テレビの電源が完全にoffの場合、httpの返答も無くなってしまいます。そこでcurlコマンドの–connect-timeoutオプションで1秒のタイムアウトを設定してあります。)
さらに、
capture_output=True
という引数をrun()メソッドに追加することで、メソッド戻り値から、標準出力の値を得ることができます。それでcurlコマンドが’0’を返したかどうかがわかります。
ここで作った関数を、
callRGZ(b'power')
のように呼び出すと、テレビの電源をoffにできます。操作に成功するとTrueが、失敗するとFalseが返ってきます。またb’1’からb’12’までを引数にして呼び出すと、チャンネルボタンの1から12を押したことと同様の操作が可能です。
赤外線送出
次に赤外線送出の部分を説明します。赤外線パターンが解明できたので、いつものようにESP32で自作しても良かったのですが、今回は以前購入してお蔵入りになっていたZigbee対応のリモコンユニットを使って楽をしようと思います。使用する赤外線パターンは電源on/offだけなので、学習させる手間も少ないです。
まずは、リモコンの「電源ボタン」を学習させました。Zigbee2MQTTのwebインタフェースで、learn_ir_codeをonにします。するとリモコンユニットの青いLEDが点灯するので、Regzaリモコンを向けて、電源ボタンを押します。
この結果、Base 64形式のデータが返ってくるので、これを以下のようなMQTTメッセージとして送り出せば、
{"ir_code_to_send": "BXYjkhFBAuAPAQOiBkECQAFAB+ALA0ABQBdAAcAHQAHgAwtAAcAPQAdAA0ABQAfAAw9RnHYjxghBAv//diPGCEEC"}'
電源ボタンに相当する赤外線パターンが送出されます。トピック名は、zigbee2mqtt/[デバイスのID]/set です。
TVアクセサリを作る
この先、プログラムをPythonで実装するのですが、それに先立って、まずはHomebridgeの設定を行いました。
HomeKitにはテレビジョンアクセサリが定義されています。今回はMqttthingプラグインを使って、このアクセサリを作っていきます。下は、Mqttthingのテレビジョンアクセサリ設定サンプルです。これを見るとわかるように、テレビジョンアクセサリは、実のところ機能が貧弱です。若者がテレビ離れしていると言われますが、HomeKitを設計しているイケてるApple社員の皆さんもテレビなど見ないので、あまり気合が入らないのかもしれません。できることは、on/offと、入力切り替えのみです。
{
"accessory": "mqttthing",
"type": "television",
"name": "<name of TV>",
"url": "<url of MQTT server (optional)>",
"username": "<username for MQTT (optional)>",
"password": "<password for MQTT (optional)>",
"caption": "<label (optional)>",
"topics":
{
"setActive": "<topic to set the status>",
"getActive": "<topic to get the status>",
"setActiveInput": "<topic to set the active input source (optional)>",
"getActiveInput": "<topic to get the active input source (optional)>",
"setRemoteKey": "<topic for publishing remote key actions (optional)>"
},
"inputs": [
{
"name": "<name for first input source>",
"value": "<MQTT value for first input source>"
},
{
"name": "<name for second input source>",
"value": "<MQTT value for second input source>"
},
...
],
"integerValue": "<true to use 1|0 instead of true|false default onValue and offValue>",
"onValue": "<value representing on (optional)>",
"offValue": "<value representing off (optional)>",
}
音量変更機能もありません。HomeKitには、スピーカーアクセサリが定義されているので、音量はこちらで設定することになっているのかもしれません。ただしスピーカの音量は0から100の絶対値で設定するので、テレビリモコンのように、「上げる」「下げる」ボタンへの対応は難しいです。またスピーカにはmute機能も提供されているのですが、テレビリモコンの「消音」ボタンは、これもトグル方式なので、正しい反映が難しいです。
また、テレビならばチャンネル切り替えが欲しいと思うのですが、それも定義されていません。その代わり、入力切り替え機能が定義されています。テレビなんか見ないで、入力切り替えてAppleTVを見ましょうという意図かと思います。ただ、テレビリモコンの「入力切替」ボタンは、入力が順繰りに選択される方式なので、これも実際の入力と合致させるのが難しいです。テレビリモコンは色々と自動化に不向きな設計だと痛感しました。
今回は、入力切り替えを、チャンネル切り替えとして使うことを考えました。チャンネルならばリモコンからチャンネル番号で指定できます。その結果、Mqttthingの設定を以下のようにしました。テレビ局名とチャンネル番号をマッピングしました。
{
"accessory": "mqttthing",
"type": "television",
"name": "Regza TV",
"caption": "Toshiba Regza TV",
"topics": {
"setActive": "mqttthing/regza/set",
"setActiveInput": "mqttthing/regza/set"
},
"inputs": [
{
"name": "NHK",
"value": "1"
},
{
"name": "NTV",
"value": "4"
},
{
"name": "TVAsahi",
"value": "5"
},
{
"name": "TBS",
"value": "6"
},
{
"name": "Tokyo",
"value": "7"
}
]
},
この結果、iPhoneやMacのホーム.appには、以下のようにテレビジョンアクセサリが表示されました。offの時は、こんな感じです。
onになって、たとえばNHKが選択されていると、
と表示されます。クリックすると
のようにチャンネル選択が可能になります。
Pythonでプログラミングする
これでホームを操作するとMQTTメッセージが流れる仕組みができました。次はMQTTメッセージに従ってWeb APIコマンドと、赤外線パターンを送出する仕組みを作ります。作成したPythonでプログラムを以下に示します。MQTTブローカーMosquittoと、このPythonプログラムは同一のRaspberry Piで動いているので、MQTTブローカーアドレスはlocalhostにしています。
#!/usr/bin/python
#
# remote control for Toshiba Regza TV from HomeKit
import time
from paho.mqtt import client as mqtt_client
import subprocess
#### globals and environmental variables ####
## Regza IR patterns for power button
RGZpower='{"ir_code_to_send": "BXYjkhFBAuAPAQOiBkECQAFAB+ALA0ABQBdAAcAHQAHgAwtAAcAPQAdAA0ABQAfAAw9RnHYjxghBAv//diPGCEEC"}'
#web API code
webcode={
b'power':'40BF12',b'1':'40BF01',b'2':'40BF02',b'3':'40BF03',b'4':'40BF04',b'5':'40BF05',b'6':'40BF06',b'7':'40BF07',b'8':'40BF08',b'9':'40BF09',b'10':'40BF0A',b'11':'40BF0B',b'12':'40BF0C'
}
# call Regza Web API
# btnname = name on the remote button
# returns True or False (success or fail)
def callRGZ(btnname):
rgzURL = 'http://192.168.xxx.xxx/remote/remote.htm?key=' + webcode[btnname]
curl = ['curl','--digest','-u','xxxxxxxx:XXXXXXX',rgzURL,'--connect-timeout','1']
cp = subprocess.run(curl,capture_output=True)
return(cp.stdout == b'0')
## MQTT ##
address='localhost'
port=1883
debug_topic='mqttthing/regza/debug'
sub_topic='mqttthing/regza/set'
pub_topic='zigbee2mqtt/0x9999999999999999/set'
client_id=b'python_194762' #something random
#username=''
#passwor=''
########### MQTT part ###########
def on_connect(client, userdata, flags, rc):
if rc==0:
print("Connection established.")
client.publish(debug_topic,"Python client connected.")
else:
print("Failed to connect: %d\n",rc)
def on_message(client, userdata, msg):
message = msg.payload
client.publish(debug_topic,"Python client on_message.")
if(message == b'false'): #power off message
callRGZ(b'power') #just power off via web API
elif(message == b'true'): #power on message
if callRGZ(b'power'): #off via web API
time.sleep(3) #it WAS on, so wait a while to send IR signal
client.publish(pub_topic,RGZpower) #on via IR signal
elif(message in webcode):
print(message)
callRGZ(message)
############main part###############
def main():
#setup
client=mqtt_client.Client(client_id)
# client.username_pw_set(username,password)
client.on_connect=on_connect
client.connect(address,port)
client.subscribe(sub_topic)
client.on_message=on_message
#main loop
while True:
client.loop()
if __name__ == "__main__":
main()
HomebridgeからのMQTTメッセージに従って、curlコマンドを呼び出しています。電源をonにする場面だけは少し工夫してあります。プログラムの49行目からの以下の部分です。
elif(message == b'true'): #power onのMQTTメッセージだった場合
if callRGZ(b'power'): #まずはWeb APIで確実にoffさせる。これが成功したら
time.sleep(3) #実はテレビはもともとonだった。その場合3秒のディレイが必要
client.publish(pub_topic,RGZpower) #改めて赤外線でonする。
最初に、Web APIで電源offさせます。万一それが動いてしまった場合は、テレビがすでに電源onだったところをoffにしてしまったことになります。その場合次の赤外線信号を受け付けるまで3秒ほど待つ必要があることがわかりました。なのでsleepさせてます。この段階で、テレビは確実にoffになっているので、赤外線経由でonさせることができます。
プログラムを自動起動する
Raspberry Piが起動した時に、自動起動するように、いつものようにsystemctlを使います。systemctlに関しては、いろいろなサイトに分かりやすい説明がありますので、ここはメモ程度に書いておきます。
まずは設定ファイルを作ります。/etc/systemd/systemに、regza.serviceという名前のファイルを作り、内容を以下にしました。ホームディレクトリがpiの場合です。そこに、servicesというディレクトリを作り、その中に上記で作成したファイルをregza.pyという名前で置いておくという設定です。
[Unit]
Description=regza
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/pi/services
ExecStart=/home/pi/services/regza.py
TimeoutStopSec=5
StandardOutput=null
Restart=always
[Install]
WantedBy = multi-user.target
この後、
$ sudo systemctl daemon-reload #設定ファイル有効化 $ sudo systemctl start regza.service #スタートさせる $ sudo systemctl status regza.service #動作を確認 $ sudo systemctl stop regza.service #ストップする $ sudo systemctl enable regza.service #電源を入れた時に動くように設定する
などのコマンドで動作させたり、次回のリブートで自動起動させたりできます。
まとめ
東芝RegzaのテレビをWeb APIとスマートリモコンで制御して、HomeKitから使えるようにしました。HomeKitのテレビジョンアクセサリは初めて使いました。on/offと入力切替しか機能がないので、リモコンとしてはあまり使えないかと思います。毎朝、出勤前にチャンネルを指定してニュースを見るとか、出勤時間にテレビを消すとかなどのオートメーションと組み合わせると有用なのかと思います。また、電源onだけをHomeKitから実行して、他の操作をスマホアプリと組み合わせて実行すれば、スマホだけから操作可能になります。
コメント