コモノポリタン

コモノ、デジモノが好きなKomonopolitan住民 (はてなダイアリーからの引っ越しです)

【Home AssistantでDIY Smart Home】赤外線リモコンのコードを解析しよう!

「Home Assistant(Hass.io)でホームオートメーション 再起動!」シリーズです。

 Broadlink RM mini 3の最新のHome Assistantインテグレーションを活用して、シーリングライトのスマート化を行いました。寄り道はしましたが、いよいよ念願のエアコン操作に向けて一歩足を進めるとしましょう。  

 新しいインテグレーションを使って赤外線リモコンの送信信号をBroadlink RM mini 3(以下RM3)で簡単に読み取ることが出来るようになりました。そのまま送信すればリモコンのボタンの模倣(エミュレーション)は簡単にできます。
 なのに何故いつまでたってもエアコンの操作をしないのか?、と疑問にお思いでしょう。ついでに、何故タイトルが「リモコンコードの解析」となっているか?についても…
 それは追々説明してゆきたいと思います。この夏の間に「ねぇぐーぐる、勉強部屋の冷房を25℃にして」という野望は実現できるのか…こうご期待?!
 (なんて記事を書いているうちに、なんだか朝晩ひんやりしてきました。エアコンの出番は?…いやきっと「残暑厳しい折」が来る!きっとくる!)

この記事の前提条件
Home Assistant 2021.8.8
HassOS 6.20
Server Raspberry Pi 4(2GB)

 上記バージョンを前提とした手順です。 (最新版では動かないこともあるかもしれませんが、私が使っている限り、備忘録を兼ねて最新化してゆきたいとは思っています)

0. なぜ赤外線リモコンコードを解析しなければならないのか?

 以前の(1年前の)記事および最近の追記記事では、リモコンの操作で送信される赤外線信号をまるっと学習してHome Assistantから送信できるようにしました。
maky-ba.hatenablog.com
maky-ba.hatenablog.com

 その記事の最後で『次はリモコンのフォーマットを解析してコードを作る、というのに取り組みましょう・・・(中略)・・・最大のメリットは、「エアコン」のコードを効率的につくれる』とも書きました。まずはそこのところを説明してみましょう。

 まずシーリングライトぐらいならリモコン操作は「全灯」「消灯」「常夜灯」が基本でしょう。ちょっと増えても「明るさ調整+」「明るさ調節ー」「色調節+」「色調節ー」「おこのみ」で10ボタンぐらいなので、ポチポチと学習させても、まあそれほど大変ではありません。(ちなみに、左のPanasonicのシーリングライトのリモコンのボタンは6個、中央の日立のシーリングライトのボタンは11個)
f:id:maky_Ba:20200713213335j:plain:w160 f:id:maky_Ba:20200713213420j:plain:w160 f:id:maky_Ba:20200713213507j:plain:w160

 しかしエアコンとなるとそうはいきません。

 一番右の三菱電機のエアコンのリモコンには、見た目には12個のボタンしかないので、一見、シーリングライトと同じようにポチポチと学習できそうです。ところが、そんなうまい話は無いのです。両者は「似て非なるもの」なのです。

 まず、シーリングライトと違ってエアコンのリモコンでは、一つのボタンに複数の値が割り当てられています。例えば「運転切替」ボタンは一回押すごとに「冷房」「除湿」「暖房」と切り替わってゆきます。「除湿調節」ボタンは「弱」「標準」「強」です。

 一番恐ろしいのは「温度設定」です。見た目は「アップ▲」「ダウン▼」のボタンしかないように見えます。シーリングライトの「明るさ調整+」「明るさ調節ー」ボタンとぱっと見は同じように見えます。リーリングライトの「明るさ調整+/-」ボタンはまさしく任意の明るさから明るさを+ーするボタンでした。ところがエアコンのリモコンの「アップ▲」「ダウン▼」ちょっと違うのです。実は「アップ▲」「ダウン▼」ボタンを押すたびにリモコンは「設定温度」を送信しているのです!

 20度の時に「暑いなぁ、ちょっと涼しくしよっと」とリモコンの▼ボタンを押しても「1度下げ」が送信されているわけではないのです。実は「19度に設定」が送信されているのです!次に▼を押した時には先ほど違う「18℃に設定」という信号が送られているのです。つまり「ダウン▼」ボタンを学習しようとすると下限から上限までのすべての温度を一つずつ学習しないといけません*1

 上記の三菱電機のエアコンのケースでは、運転:3種類、除湿:3種類、 温度(18度~31度):14種類、、風向:7種類、風速:4種類です。では3+3+14+7+4=31種類分、ぽちぽち31回学習すれば良いのでしょうか?これなら、ちょっと大変ではありますが、まあ我慢できなくもありません。

 ところどっこい、そうは問屋が卸さないのです。

 最大の恐怖は、実はエアコンのリモコンは全部の設定を一度に送っている点にあります。
 例えば、先ほどの温度「ダウン▼」を押したときに「運転切替:冷房、温度:20度、除湿調節:なし、パワフル:Off、風速:自動、風向:自動、内部クリーン:Off」といった液晶に表示されている情報をすべて一度に送信しているのです*2

 上記の三菱電機のエアコンのケースでは、運転:3種類、除湿:3種類、 温度(18度~31度):14種類、、風向:7種類、風速:4種類、とここまでで単純に掛け算したら3000超の組み合わせ*3。まあ、除湿に温度設定がないので、2x14x7x4 + 3x7x4 = 854通りとなります。ほかにパワフルとか、内部クリーンとかもあったりして…。これをポチポチしてたら日が暮れるどころの騒ぎではありません。1分間に1通りの信号を読み取ったとしても、854通りで14時間…苦行以外のなにもでもありませんね。

 ここで「コード解析」が役に立ちます。うまくコードを探れば、掛け算ではなく足し算ですむのです。854通りが、3+3+14+7+4=31通り、さらに温度なんかは3つ4つ調べれば十分なので20通りぐらい読み取れば良いということになります。14時間が20分に!!

 さあ、取り掛かりましょう!

1. 赤外線リモコンのコードの基本

1.1. 黒豆(RM3)の読み取り結果を見る

 黒豆(Broadlink RM mini 3)を使ってHome Assistantで学習した赤外線コードJgACAW03EA0PDQ8pECkPDQ8pEA0PD…は、Base64エンコードされたものです。Base64についてはこちらを参照。
 デコードするとバイナリーデータが得られます。先頭から1バイトずつの意味を下記の表にまとめます。

オフセット 説明
0 0x26 0x26:IR(赤外線信号(変調波は38kHz))、0xb2:RF(433MHzの電波信号)。
黒豆は赤外線リモコンなので0x26一択ですが、RM ProあたりはIRとRFの両方対応だったりします。
1 0x00 信号の繰り返し(0=繰り返しなし、1=2度送信、…)
2
3
0x04
0x02
これ以降のペイロードのデータ長をリトルエンディアン表記で。
(この例では、0x0204=516バイト)
4


送信される赤外線信号を格納するペイロード部分。
リーダー部、データ部、トレーラ部から成る。

 ペイロード部分(赤外線信号部分)の説明の補足を。
 赤外線の信号は、ベースとなる搬送波(Carrire Frequency。黒豆だと38kHz)をオン、オフして、0、1のビットデータを表します。これはリモコンに使われている赤外線以外にも自然界にも赤外線が多く含まれるので、区別できるように38kHzで細かくオンオフしています。ですので「オン」と言っても細かく見るとオンオフを繰り返しています(下図参照)。ですが、それはいったん忘れてOKです。
 さらにどのようにデータを表すかというと、下記の例ですと、425μsec(マイクロ秒)のオン部分と425μsecのオフ部分がセットになってビットデータを表します。どんな長さのOn/Offの組み合わせが0なのか、1なのかはリモコンの方式によって解釈が変わります。次で言うところの家製協フォーマットのリモコンなら、オンとオフが同じ時間で大体425μsecの場合が0を、オンとオフが1:3でそれぞれ425μsecと1275μsecのセットが1を表しています。

f:id:maky_Ba:20210815222245j:plain

 ちなみに、RM3では赤外線信号のオン・オフの長さはμsec単位ではなく、RM3の内部信号のパルス数で表されています。215Hz=32.768 kHzの内部信号だそうです。ですので1単位(ここでは勝手に黒豆パルスと呼びます)が30.51μsecぐらいの時間の長さを表します。先ほどの例で言えば、家製協フォーマットの0は、オンに14黒豆パルス、オフに14黒豆パルスとなります。

1.2. 赤外線リモコンの仕様を知る

 赤外線リモコンにいくつかのフォーマットがあります。日本で有名なのは家電製品協会(家製協/AEHA)フォーマット、NECフォーマット、SONYフォーマット等です。ペイロード部分(赤外線信号部分)でどのような形で信号を送るかによって各フォーマットが決まります。
 さらに少し分解すると、データを送る前にリモコンコードであることを示すリーダー部、次にデータ部、最後にコードの終わりを示すトレーラー部の3つのパートからなり、それがどのように定義されているかで各フォーマットが決まります。

1.2.1 家製協(AEHA)フォーマット

 家電製品協会(=家製協、AEHA(Association for Electric Home Appliances))のフォーマットで、データ部が可変長なところが特徴。Panasonicのシーリングライト等が採用しています。

ペイロード データ 説明
長さの単位T=350μsec~500μsec。
リーダー部
Leader
On(8T), Off(4T)
データ部
Data
0: On(1T), Off(1T)
1: On(1T), Off(3T)
1~2 バイト目=カスタムコード
3 バイト目byte目以降=データコード
トレーラー部
Trailer
On(1T), Off(n) n>8msec かつ赤外線コード全体
(リーダー、データ、トレーラー)
で130msecになるようにnを計算。
黒豆では0x0d, 0x00 0x0d 0x05で固定。

1.2.2 NECフォーマット

 一番古いフォーマットで多くの機器で使われています。先頭の2バイトはメーカーの識別コードで、NECが管理しているとのこと(いまもでしょうか?)
 データ部は4バイト(32ビット)固定長ですが、先頭2バイトはメーカーコード、最後の1バイトはチェック用なので実質1バイトのコードです。足りるのかな?

ペイロード データ 説明
長さの単位T=562.5μsec
リーダー部
Leader
On(16T), Off(8T)
データ部
Data
0: On(1T), Off(1T)
1: On(1T), Off(3T)
1~2 バイト目=カスタムコード
3バイト目=データコード
4バイト目=3バイト目の補数。
(トレーラー部)
(Trailer)
On(1T), Off(nn) 4バイト固定長なのでトレーラー部は
不要ですが、黒豆用に設定必要です。
On(1T)=0x0d, Off(nn)=0x00 0x0d 0x05を設定の事。

1.2.3 SONYフォーマット

ペイロード データ 説明
長さの単位T=600 μsec
リーダー部
Leader
On(4T), Off(1T)
データ部
Data
0: On(1T), Off(1T)
1: On(2T), Off(1T)
12ビット/15ビット/20ビット長のどれか。
トレーラー部
Trailer
On(1T), Off(nT) 赤外線コード全体(リーダー、
データ、トレーラー)で75T=45msecに
なるようにnを計算する。
黒豆ではOn(1T)=0x0d, Off(nT)=0x00 0x0d 0x05で固定

1.3. コード解析と赤外線信号合成の流れ

 RM3のフォーマットおよび各赤外線リモコンフォーマットがわかりましたので、解析と合成の流れの全体感を下に示します。

f:id:maky_Ba:20210817211758j:plain

 まずHome Assistantで学習した時に取得できるものは、RM3フォーマットをbase64エンコードしたものです(「RM3学習データ」)。base64をデコードすると16進数の文字列になります(「RM3フォーマット」)。
 肝心の信号を表すのは、5バイト目(10文字目)以降になります。基本的にはOnの長さ、Offの長さをそれぞれ1バイトで表します(例外は長さが255以上の時)。μsecで長さを表したものをここでは「リモコン赤外線信号(汎用)」と呼びます。
 オン、オフの長さの組み合わせで、0,1の情報(「赤外線リモコンコード」)が分かります。赤外線信号を表すのは長い0,1のビット列になります(なお、これを16進数表記にするときに、LSBファーストにするか、MSBファーストにするかで16進数の表記が変わります。要注意です)。リモコンの様々なボタンのコードを並べてみると法則性がわかったりします。

 「赤外線リモコンコード」を得る、ここまでが信号の「解析」です。

 さらにこのコードを基に、逆に進めてRM3用の学習データを作るのが信号の「合成」です。

 前述したとおり、学習していない信号であっても「赤外線リモコンコード」を作り出せば、オン、オフの信号の長さのデータを合成・生成することができ、さらにはHome Assistant + RM3で使えるbase64の学習データ(相当)を作ることが出来ます。

2. コード解析の準備

2.1. 赤外線リモコンのコード解析用Pythonプログラムを書く

 前の節の赤外線リモコンの信号仕様を踏まえてpythonでプログラムを書いてみました。赤外線リモコンコードをbit表示(ビット列表示)とhex表示(16進数表記)で吐き出すpythonプログラムを載せておきます。
 なおPythonそのものに不慣れ(「初めてのPython」は参考書として買ったものの…)なので、とりあえず動く版です。例外処理なんて一つも書いていません!(いばるな、ですよね)
 そのうちリファクタリングしたいなあ。  

#! /usr/bin/env python3
import base64
import json
import sys

#
# 関数Fuction - 黒豆(Black Bean)赤外線信号解析(IR signal analizer)
#  引数: base64フォーマットの赤外線信号
#  戻り値: 辞書形式の解析結果
#
def iranalyze(bb_base64):
    # 黒豆の学習データ(base64フォーマット)を16進数に変換
    bb_decoded = base64.b64decode(bb_base64)
    bb_hex = [format(x, 'x') for x in bb_decoded]

    # 黒豆学習データを前半(ヘッダー)とペイロード(データ本体部分)に分割
    # 黒豆データ構造(offset)
    #   0 - 3 byte: ヘッダー部
    #         0 byte: 0x26 = IR, 0xb2 = RF(433MHz)
    #         1 byte: Repeat (0=no repeat, 1=send twice, )
    #       2-3 byte: length of payload (little edian)
    #   4 -   byte: データ本体(Payload)部分
    bb_header = bb_hex[0:3]
    bb_payload = bb_hex[4:]

    # 16進数をon/offの信号(パルス幅)に変換 (μ秒(usec))表記)
    ## (1)黒豆payloadを取り出し(黒豆のサンプリングでの幅)「リモコン赤外線信号(RM3)」
    signal = []
    multibyte = 0
    for hex in bb_payload:
        if int(hex,16) == 0:
            multibyte = 1
            continue
        elif multibyte == 1:
            l_hex = hex
            multibyte+=1
            continue
        elif multibyte == 2:
            signal.append(int(l_hex, 16)*256 + int(hex, 16))
            multibyte = 0
            continue
        else:
            signal.append(int(hex,16))
    ## (2)実際の信号幅に変換「リモコン赤外線信号(汎用)」
    ##    黒豆のサンプリング幅=1/(2^15) おおよそ30.52 usec
    signal_usec = [t*30.52 for t in signal]

    # フォーマットを判別し、0,1のコードを解析
    leader_index =  [i for i, x in enumerate(signal_usec) if x > 2100 and x <9900 ]
    signal_usec = signal_usec[leader_index[0]:]    # リーダー部の前のゴミを除去
    code = []

    if signal_usec[0] > 2100 and signal_usec[0] < 2600:         # SONYフォーマット Leader ON(4T)=600*4 micro sec
        ### 1) SONY Format
        ###
        ir_format = "SONY"
        # Leader ON の幅からUnit Time (ut)を計算
        # ut = 600   # SONYフォーマットの固定値を使う場合用
        ut = signal_usec[0] / 4                                 # Leader: ON(4T)OFF(1T)
        # SONY Formatの各種パルス幅(μsec)
        leader_on  = ut * 4
        leader_off = ut * 1
        bit_0_on  = ut * 1
        bit_0_off = ut * 1
        bit_1_on  = ut * 2
        bit_1_off = ut * 1
        # 時間幅からut単位に変換
        signal_ut = [int(round(t/ut, 0)) for t in signal_usec]
        i = 0
        while i < len(signal_ut):
            if   signal_ut[i] == 4 and signal_ut[i+1] == 1:
                code.append('L')
            elif signal_ut[i] == 1 and signal_ut[i+1] == 1:
                code.append(0)
            elif signal_ut[i] == 2 and signal_ut[i+1] == 1:
                code.append(1)
            elif signal_ut[i] == 1:
                code.append(0)
                code.append('T')
            elif signal_ut[i] == 2:
                code.append(1)
                code.append('T')
            else:
                code.append('U')
            i+=2

    elif signal_usec[0] >2600 and signal_usec[0] < 4400:        # 家製協(AEHA)フォーマット Leader ON(8T)=2800~4000 micro sec
        ### 2) AEHA Format
        ###
        ir_format = "AEHA"
        # Leader ON の幅からUnit Time (ut)を計算
        # ut = 425   # AEHAフォーマットの固定値を使う場合用
        ut = signal_usec[0] / 8                                 # Leader: ON(8T)OFF(4T)
        # AEHA Formatの各種パルス幅(μsec)
        leader_on  = ut * 8
        leader_off = ut * 4
        bit_0_on  = ut * 1
        bit_0_off = ut * 1
        bit_1_on  = ut * 1
        bit_1_off = ut * 3
        # 時間幅からut単位に変換
        signal_ut = [int(round(t/ut, 0)) for t in signal_usec]
        i = 0
        while i < len(signal_ut):
            if   signal_ut[i] == 8 and signal_ut[i+1] == 4:
                code.append('L')
            elif signal_ut[i] == 1 and signal_ut[i+1] == 1:
                code.append(0)
            elif signal_ut[i] == 1 and signal_ut[i+1] == 3:
                code.append(1)
            elif signal_ut[i] == 1:
                code.append('T')
            else:
                code.append('U')
            i+=2

    elif signal_usec[0] >8000 and signal_usec[0] < 9900:        # NECフォーマット LeaderON(16T)=9000 micro sec
        ### 3) NEC Format
        ###
        ir_format = "NEC"
        # Leader ON の幅からUnit Time (ut)を計算
        # ut = 562.5   # NECフォーマットの固定値を使う場合用
        ut = signal_usec[0] /16                                 # Leader: ON(16T)OFF(8T)
        # NEC Formatの各種パルス幅(μsec)
        leader_on  = ut * 16
        leader_off = ut * 8
        bit_0_on  = ut * 1
        bit_0_off = ut * 1
        bit_1_on  = ut * 1
        bit_1_off = ut * 3
        # 時間幅からut単位に変換
        signal_ut = [int(round(t/ut, 0)) for t in signal_usec]
        i = 0
        while i < len(signal_ut):
            if  signal_ut[i] == 16 and signal_ut[i+1] == 8:
                code.append('L')
            elif signal_ut[i] == 1 and signal_ut[i+1] == 1:
                code.append(0)
            elif signal_ut[i] == 1 and signal_ut[i+1] == 3:
                code.append(1)
            elif signal_ut[i] == 1:
                code.append('T')
            else:
                code.append('U')
            i+=2

    else:
        print("unknown format")
        signal_collection = collections.Counter(signal)
        print("Signal Set(pulse width[T], count, ...): ", signal_collection)

    # 辞書形式での書き出し
    padding = "000"
    signal_dict = dict()
    signal_dict['ir_format'] = ir_format
    signal_dict['unit_time'] = round(ut,2)
    signal_dict['leader_on']  = round(leader_on)
    signal_dict['leader_off'] = round(leader_off)
    signal_dict['bit_0_on']  = round(bit_0_on)
    signal_dict['bit_0_off'] = round(bit_0_off)
    signal_dict['bit_1_on']  = round(bit_1_on)
    signal_dict['bit_1_off'] = round(bit_1_off)
    signal_dict['signal_bit'] = {}
    signal_dict['signal_hex'] = {}

    i = 0
    signal_bit = dict()
    signal_hex = dict()
    while 'L' in code and 'T' in code:  # LからTまでが一つの信号(1行で出力)
        if code[code.index('L')+1:code.index('T')] == []:
            break
        elif 'U' in code[code.index('L')+1:code.index('T')]:
            break
        # 2進数表記に変換 「赤外線リモコンコード(bit表示)」
        signal_bit['Repeat'+str(i)] = ','.join([str(a) for a in code[code.index('L')+1:code.index('T')]])
        # 16進数表記変換  「赤外線リモコンコード(hex表示)」
        signal_bitstr = '0b'+"".join([str(a) for a in code[code.index('L')+1:code.index('T')]])
        l = len(signal_bitstr) - 2
        if l%4 != 0:
            signal_bitstr = signal_bitstr+padding[(l%4-1):3]
        signal_bitnum = int(signal_bitstr, 2)
        signal_hex['Repeat'+str(i)] = '0x{:x}'.format(signal_bitnum)
        # 次のリピートへ
        code = code[code.index('T')+1:]
        i += 1

    # JSONファイル用に辞書形式でデータ保存
    signal_dict['signal_bit'] = signal_bit
    signal_dict['signal_hex'] = signal_hex
    return signal_dict


#
# メイン:
# Home Assistantの黒豆(Broadlink RM3 mini)での赤外線学習結果の解析
#   Usage:
#     ir_analyze.py <input file> <output file>
#      <input file> : JSONファイル(~/.storage/にある学習結果のファイル)
#      <output file> : 解析結果。JSONファイル形式
#
with open(sys.argv[1], 'r') as f:
    rm3codes_dict = json.load(f)
# future usage: check data file
# [manufacturer, device_type, rm3mac, contents] = rm3codes_dict['key'].split('_')

output_dict = dict()
output_dict['version'] = "1"
output_dict['remote'] = "Broadlink_RM3_mini"
output_dict['data'] = {}

for device in rm3codes_dict['data']:
    output_dict['data'][device]={}
    for command in rm3codes_dict['data'][device]:
        output_dict['data'][device][command] = {}
        d = iranalyze(rm3codes_dict['data'][device][command])
        output_dict['data'][device][command] = d

with open(sys.argv[2], 'w') as f:
    json.dump(output_dict, f, indent=4)

 「とりあえず動く版」なのは身の回りにあまりNEC方式やSONY方式のリモコンが無く、それらのリモコンが正しく解析できているか若干不安だからです。コストコで買ったLEDランプがNEC形式だ、と判定されていますがさてはて。SONY方式はSONYリモコンの学習に迫られたら動作を確認して見ますね。

 そうだ、一点、説明を。
 このプログラムでは16進数表記(Hex表示)にするときに、一番先頭のビットがMSBとして変換してます。

順番 #1 #2 #3 #4 #5 #6 #7 #8
データ
ビット列
0 0 1 1 0 1 0 0
(MSB) D7 D6 D5 D4 D3 D2 D1 D0 (LSB)
16進数表記 3 4
(LSB) D0 D1 D2 D3 D4 D5 D6 D7 (MSB)
16進数表記 C 2

 赤外線コードの情報を載せているサイトによっては、並びの順に下から上(LSBからMSB)と考えて16新表記化しているサイトもあるようなので、ちょっと気を付けてください。

2.2 使い方

 Home Assistantで黒豆(RM3)を用いて学習した結果ファイル(JSON形式ファイル)を入力ファイルとして指定します。1ファイル(=1リモコンデバイス)に含まれる複数の学習結果をまとめて解析できるようにしてあります。
 今回のPythonプログラムは、私はカスタムコンポーネント用のフォルダにmy_ir_remoteフォルダをつくり、そこに格納しました。忘れずに実行可能ファイルにしておきます($ chmod 755 <file名>)。

$ cd ~/config/custom_components/my_ir_remote
$ ./ir_analyze.py <input file> <output file>

3. 解析を試してみる

 上の方で写真を載せたパナソニックのシーリングライト用リモコン(HK9327K)のコードを解析してみましょう。「全灯」「お好み」「(常夜灯)」「暗」「明」「消灯」の6個のボタンを学習します。

3.1. 入力ファイル

 まず下記の記事に従ってポチポチと6ボタン分学習します。
maky-ba.hatenablog.com

 そうすると~/config/.storage/以下にJSON形式の学習結果ファイルが格納されます。broadlink_remote_<deviceのWiFi MACアドレス>_codesという名前のファイルが対象のファイルです。

3.2. 出力ファイル

 お手製変換プログラムで変換してみました。

$ cd ~/config/custom_components/my_ir_remote
$ ./ir_analyze.py ~/config/.storage/broadlink_remote_<device MAC address>_codes pana_IR_remote_codes

 出力ファイルの内容は下記のとおりです。細かい説明は省きますが、見ればおおよそわかると思います。
 leader_on leader_off等のon offの数値は、フォーマット(ir_format)と単位時間(unit_time)が判れば計算可能ですが、一々計算するのが面倒なので計算したものを出力させてます。

{
    "version": "1",
    "remote": "Broadlink_RM3_mini",
    "data": {
        "HK9327K": {
            "on": {
                "ir_format": "AEHA",
                "unit_time": 434.91,
                "leader_on": 3479,
                "leader_off": 1740,
                "bit_0_on": 435,
                "bit_0_off": 435,
                "bit_1_on": 435,
                "bit_1_off": 1305,
                "signal_bit": {
                    "line1": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,0,0,1,1,0,1,0,0,1,0,1,0,0,1,0,0",
                    "line2": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,0,0,1,1,0,1,0,0,1,0,1,0,0,1,0,0",
                    "line3": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,0,0,1,1,0,1,0,0,1,0,1,0,0,1,0,0",
                    "line4": ""
                },
                "signal_hex": {
                    "line1": "0x344a9034a4",
                    "line2": "0x344a9034a4",
                    "line3": "0x344a9034a4"
                }
            },
            "on_custom": {
                "ir_format": "AEHA",
                "unit_time": 408.2,

(中略)

            },
            "off": {
                "ir_format": "AEHA",
                "unit_time": 419.65,
                "leader_on": 3357,
                "leader_off": 1679,
                "bit_0_on": 420,
                "bit_0_off": 420,
                "bit_1_on": 420,
                "bit_1_off": 1259,
                "signal_bit": {
                    "line1": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,1,1,0,0,1,0,0",
                    "line2": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,1,1,0,0,1,0,0",
                    "line3": "0,0,1,1,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,1,1,0,0,1,0,0"
                },
                "signal_hex": {
                    "line1": "0x344a90f464",
                    "line2": "0x344a90f464",
                    "line3": "0x344a90f464"
                }
            }
        }
    }
}

3.3. 先人の叡智と突き合わせる

 正しく変換・解析されたか確認したいので、先人の知恵を借ります。FUTABA WEBさんの所に、いろいろと解析した結果がDBになっています*4
 ズバリではありませんが、松下(=パナソニック)のHK9328というリモコンのデータが下記に乗っています。今回はリモコンのチャネル1を学習したので、下記では「Page1を参照」ということになります。
www.256byte.com

 ONのところに「データ:0x344A9034A4」とあります。一方、3.2で見た出力ファイルには「"on" (中略) "line1": "344a9034a4"」と表示されています*5。おお!うまくいっているようです!

 信号の幅も確認しておきましょう。まあいい感じではないでしょうか。

FUTABA
今回
ヘッダー
Leader
コード0
bit_0
コード1
bit_1
on off on off on off
FUTABA 3500us 1700 us 400 us 550 us 400 us 1450 us
今回 3479 1740 435 435 435 1305

4. パナソニックのシーリングライトリモコンの簡単な解析

 3.で変換したコマンドごとのビット列(40bit)を並べてみました。
f:id:maky_Ba:20210902202738j:plain
 よーくみると、コマンドやチャネルの部分が判ります(あ、チャネルは違うチャネルでボタンを押して学習させないとわかりません、あしからず)。白抜きのあたり(21~24ビット目、30~32ビット目)が怪しいですね。なんかコマンド潜んでいるような気も…。

5. つづく(力尽きる)

 ということで、Home Assistantで学習したRM3学習データをコードに変換するところまではいきました。次はコードからRM3学習データを作る信号合成ですね。
 ふぅ、まだまだエアコンを快適にコントロールするのには道が長い。このままではまた一年放置か…。

*1:もっと恐ろしいことに、上位機種では0.5度単位で設定可能なのです!!

*2:世の中の学習リモコンで「エアコンがうまく学習できない」という時は、このすべて送信という組み合わせの多さと、そもそも送信信号長が長く(三菱電機のエアコンで18バイト)学習しきれないというケースが多いみたいですよ

*3:ちなみに、この三菱電機のリモコンは普及機のやつです。上位機種はムーブアイだのなんだのが付いていて機能数はさらに増加!

*4:ちょっと心配なのはこのサイトの最終記事が2012年というところ。サイト無くなったりしませんよね…そこだけが心配…

*5:ちなみにFUTABAさんのところはデータを16進数表記しているので、私のお手製プログラムもHEX表記も出力できるようにしました