02 July 2024

Raspberry Piで赤外線リモコンを学習し発信する(irrp.py版)

2014年6月の『Raspberry Piで赤外線リモコンを学習し発信する』の記事で用いたソフトウエアは LIRC だった。しかし、最新のRPi OSをインストールした RPi Zero Wで動作させることができなかったため、irrp.py : IR Record and Playback (pigpio) を用いて赤外線信号を送受信する方法を取ることにした。

Raspberry Piに接続する赤外線送受信回路

2014年6月の『Raspberry Piで赤外線リモコンを学習し発信する』の記事で用いた回路を、そのまま流用する。

20140721-rpi_ir_test_circuit.png

リモコン赤外線信号のパケット構造

フレーム構造と、その送信フレームを使ったパケット構造は、次のようになっていると「推測」できる。

20240701-ir-timing.png
赤外線リモコン信号のフレーム(NEC方式、家電協方式) (SVGファイルをダウンロードする

20240701-ir-packet.png
赤外線リモコン信号のパケット(NEC方式、家電協方式) (SVGファイルをダウンロードする

これらの情報を頭に入れておいた上で、受信した赤外線データをエンコードするときの「仕分け」を行う。

利用するソフトウエアを用意する

pigpio

pigpioライブラリをaptコマンドでインストールする。

sudo apt install pigpio
sudo apt install python3-pigpio

赤外線信号を送受信するPythonスクリプト irrp.py

pigpio の作者Webサイト(http://abyz.me.uk/rpi/pigpio/examples.html)より、次のソフトウエアをダウンロードする。

  • IR Record and Playback (irrp_py.zip)
    2015-12-21 This script may be used to record and play back arbitrary IR codes.

または、当Webサイトに保存したirrp_py.zipをダウンロードする作者Webサイトのものを、そのまま転載)。

このスクリプトは、赤外線信号を受信し、Hi/Loの持続時間(nsec ナノ秒)をjson形式のテキストファイルに保存する。

$ python3 irrp.py -r -g [GPIO port#] -f [output.json] --post [トレイラー msec] --no-confirm [ボタン配列名1] [ボタン配列名2] ... [ボタン配列名n]
json形式のファイル例
{"ボタン配列名1": [9066, 4390, 683, 438, 683, 1571, 683, 438, ...]}

Hi/Loの持続時間(nsec ナノ秒)をデコードするスクリプト decode.py

@takusan64氏のWebサイト『 リモコンが家電を動かす仕組みを理解する【ラズパイで遊ぼう!】』より、「赤外線情報を成形する」セクションに掲載されている decode.py をダウンロードする。

  • 先ほど生成した赤外線ファイルに書かれた情報を見やすくしましょう。
    簡単なPythonスクリプトを作成したので、実行してみます。
    Pythonコード(長いので折りたたんでます)

または、当Webサイトに保存したdecode.py.zipをダウンロードする作者Webサイトのものを、そのまま転載)。

このスクリプトは、一つ前のセクションでirrp.pyを使って保存したHi/Loタイミングの配列が格納されたファイルを読み込んで、NECフォーマットや家電協フォーマットの16進数にデコードしてくれるものだ。

$ python3 decode.py -t [1T相当のマイクロ秒] -f [配列が保存されたjsonファイル名] volup

T: [20, 10, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 90, 20, 4, 1, 218, 20, 4, 1, 218, 20, 4, 1]
bin: [0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0]
hex: ['5', 'E', 'A', '1', '5', '8', 'A', '7']

irrp.py で赤外線信号をキャプチャするサンプル例

キャプチャする赤外線信号の「末端」を判別するため、トレイラーの長さを指定する必要がある。irrp.pyのデフォルト値は13ミリ秒だが、NECフォーマットと家電協フォーマットで決められているトレイラー長さを手動設定したほうが、より精度の高いキャプチャが可能になる。

NECフォーマットの場合のトレイラーの長さ(ミリ秒)

irrp.pyで赤外線信号をキャプチャするとき、トレイラーのところで処理を終了したい。(--post 引数の)デフォルトでは15ミリ秒が設定されているが、不要な部分までキャプチャしすぎる場合がある。

まず、NECフォーマットの場合のトレイラー長(ミリ秒)を計算で求める。ここでは、パケットが標準的な4バイトの場合を前提として計算する。

・1パケットは108msごとに送信される
・1T=560us, 3T=1690us, 8T=4500us, 16T=9000us の標準的なタイミングとする

データ部(4bytes)の所要時間が最大の場合、つまり全ビットが1 (1T+3T) のときに、トレイラー部の所要時間が最短になる

・Leader = (16T + 8T) = 9000 + 4500 us → 13.5 ms
・Data = (1T + 3T) × 4 byte × 8 bit = (560+1690)×4×8 = 72000 us → 72 ms
・Trailer = 108 - ( Leader + Data ) = 108 - (13.5+72) = 22.5 ms

データ部(4bytes)の所要時間が最小の場合、つまり全ビットが0 (1T+1T) のときに、トレイラー部の所要時間が最長になる

・Leader = (16T + 8T) = 9000 + 4500 us → 13.5 ms
・Data = (1T + 1T) × 4 byte × 8 bit = (560+560)×4×8 = 35840 us → 35.8 ms
・Trailer = 108 - ( Leader + Data ) = 108 - (13.5+35.8) = 58.7 ms

結果として、トレイラー部の長さは 22.5〜58.7ミリ秒の間である。

家電協フォーマットの場合のトレイラーの長さ(ミリ秒)

仕様として、トレイラー部の長さは8000マイクロ秒(8ミリ秒)以上である。

赤外線信号をキャプチャする。サンプル例1(YAMAHAのAVアンプ)

フォーマットが確定していない場合、トレイラー部の長さを決め打ちできないため、デフォルト値(13ミリ秒)や長め(100ミリ秒等)を指定して、試しに受信して中身を観察してみる。

フォーマットを判別するため、リピート信号も含め長めにキャプチャした例
$ python3 irrp.py -r -g 4 -f testfile.json --post 100 --no-confirm button1 button2

Recording
Press key for 'button1'
Short code, probably a repeat, try again
Okay
Press key for 'button2'
Short code, probably a repeat, try again
Okay

$ cat testfile.json 

{"button1": [9066, 4390, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 1571, 683, 1571, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 438, 683, 438, 683, 438, 683, 1571, 683, 1571, 683, 1571, 683, 438, 683, 1571, 683, 1571, 683, 438, 683, 438, 683, 438, 683, 438, 683, 438, 683, 1571, 683, 438, 683, 438, 683, 1571, 683, 1571, 683, 1571, 683, 39545, 9066, 2146, 683, 95866, 9066, 2146, 683, 95866, 9066, 2146, 683],

 "button2": [9066, 4390, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 1571, 683, 1571, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 438, 683, 438, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 1571, 683, 438, 683, 438, 683, 438, 683, 1571, 683, 438, 683, 1571, 683, 438, 683, 438, 683, 1571, 683, 1571, 683, 1571, 683, 39545, 9066, 2146, 683, 95866, 9066, 2146, 683, 95866, 9066, 2146, 683]}

注目点は赤で着色した部分。

まず、リーダー部が Hi 9066us, Lo 4390us であることから、これはNECフォーマットの信号だと分かる。

NECフォーマットの場合、標準的な値では 1T=560us, 3T=1560us なので、キャプチャした値がこれに合致しているかチェックすると

・ビット 1 : Hi 683us + Lo 438us
・ビット 0 : Hi 683us + Lo 1571us

という結果になっている。

そして、1Tと3Tから外れる値が初めて現れているのが、赤で着色した39545usの部分。引数--postで130msecを指定しているので、39.5msecではキャプチャ終了せず、その後に続いている信号も拾ってしまっている。この39.5msecのLo信号はトレイラー部である

そして、トレイラー部の後ろで緑に着色した部分は、リピート信号。ボタンが押しっぱなしなので、リピート信号が送信されているのである。

この余計な部分も受信したjsonファイルを decode.py で処理すると結果が不安定になるため、適切なトレイラー長さ(--post引数に指定する値)で決め打ちしてキャプチャを行う。

このサンプルの場合は、NECフォーマットのトレイラー長さ22.5〜58.7ミリ秒にじゅうぶん反応する値である 20ミリ秒を採用する。

1パケット分のみ正しくキャプチャできた例
$ python3 irrp.py -r -g 4 -f testfile.json --post 20 --no-confirm button1 button2
Recording
Press key for 'button1'
Short code, probably a repeat, try again
Okay
Press key for 'button2'
Short code, probably a repeat, try again
Okay

$ cat testfile.json

{"button1": [9070, 4382, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 1573, 680, 1573, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 441, 680, 441, 680, 441, 680, 1573, 680, 1573, 680, 1573, 680, 441, 680, 1573, 680, 1573, 680, 441, 680, 441, 680, 441, 680, 441, 680, 441, 680, 1573, 680, 441, 680, 441, 680, 1573, 680, 1573, 680, 1573, 680],

 "button2": [9070, 4382, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 1573, 680, 1573, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 441, 680, 441, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 1573, 680, 441, 680, 441, 680, 441, 680, 1573, 680, 441, 680, 1573, 680, 441, 680, 441, 680, 1573, 680, 1573, 680, 1573, 680]}

赤外線信号をキャプチャする。サンプル例2(Panasonicの照明)

まず、--post 100 (トレイラー 100ミリ秒)を指定して、長めにキャプチャする。

フォーマットを判別するため、長めにキャプチャした例
$ python3 irrp.py -r -g 4 -f testfile.json --post 100 --no-confirm button1 button2

Recording
Press key for 'on'
Short code, probably a repeat, try again
Okay
Press key for 'off'
Short code, probably a repeat, try again
Okay

$ cat testfile.json 

{"button1": [3551, 1658, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 74298, 3551, 1658, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 337, 532],

 "button2": [3551, 1658, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 74298, 3551, 1658, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 337, 532, 1207, 532, 1207, 532, 337, 532, 337, 532, 337, 532, 337, 532, 1207, 532, 1207, 532, 1207, 532, 1207, 532, 337, 532, 337, 532]}

この例では、リーダーが Hi 3551us, Lo 1658usであり、家電協フォーマットの Hi 3200us, Lo 1600us に一致している。

家電協フォーマットの場合、標準的な値では 1T=400us, 3T=1200us なので、キャプチャした値がこれに合致しているかチェックすると

・ビット 1 : Hi 532us + Lo 337us
・ビット 0 : Hi 532us + Lo 1207us

という結果になっている。これも家電協フォーマットにほぼ一致する結果だ。

そして、1Tと3Tから外れる値が初めて現れているのが、赤で着色した74298usの部分。これがトレイラー部なので、キャプチャする場合の --post に設定する引数はデフォルト値の13ms(13000us)で事足りる。

1パケット分のみ正しくキャプチャできた例
$ python3 irrp.py -r -g 4 -f testfile.json --no-confirm button1 button2
Recording
Press key for 'on'
Short code, probably a repeat, try again
Okay
Press key for 'off'
Short code, probably a repeat, try again
Okay

$ cat testfile.json 
{"button1": [3548, 1663, 532, 338, 532, 338, 532, 1208, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 1208, 532, 1208, 532, 338, 532, 1208, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 1208, 532, 1208, 532, 1208, 532, 1208, 532, 338, 532, 338, 532],

 "button2": [3548, 1663, 532, 338, 532, 338, 532, 1208, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 338, 532, 1208, 532, 1208, 532, 338, 532, 338, 532, 338, 532, 338, 532, 1208, 532, 1208, 532, 1208, 532, 1208, 532, 338, 532, 338, 532]}

decode.pyで16進数形式にデコードする

サンプル例1のHi/Loタイミング配列をデコードしてみる。1T=441usなので、-t 431 としてスクリプトを実行する。

サンプル例1のデコード
$ python3 decode.py -t 431 -f testfile.json button2

T: [20, 10, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 90, 20, 4, 1, 218, 20, 4, 1, 218, 20, 4, 1]
bin: [0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0]
hex: ['5', 'E', 'A', '1', '5', '8', 'A', '7']

サンプル例2もデコードしてみる

サンプル例2のデコード
$ python3 decode.py -t 337 -f light_pana.json button1
T: [10, 4, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1]
bin: [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, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0]
hex: ['3', '4', '4', 'A', '9', '0', 'A', 'C', '3', 'C']