Featured image of post 赤外線リモコン信号の解析アプリケーション

赤外線リモコン信号の解析アプリケーション

リモコン信号の信号解析

最終更新 2020/06/18 00:00 JST

リモコン信号の信号解析アプリケーション

信号解析アプリケーション

以前に用意したアプリケーションのおまけに解析器を付けておきました。
メニューのInfra-redページでリモコン信号をコピペすると確認できます。

注: ↑は成り行きで追加していたらいつの間にかフロントヘビーに育ってしまったので今度分割します。

React Native for Windowsを使ってデスクトップアプリにしました。
このページへどうぞ。

三菱電機とパナソニック製エアコンのリモコン信号を解析する

三菱電機とパナソニック製エアコンのリモコン信号の資料 on GitHub

三菱電機とパナソニック製エアコンのリモコン信号の資料が GitHub にあるのを見つけたので, それを解析するソフトウェアを実装した。

三菱電機製エアコンの信号解析

資料によると第1フレームと第2フレームは共通の信号なので片方を解析すればよいようだ。

フレームは18バイト。

  • 温度 8バイト目
  • 運転 6バイト目
  • 電源 6バイト目
  • CRC 18バイト目 (前の17バイトすべて足した値の mod 256)

ということのようですね。

自分は三菱電機製エアコンを持っていないから, 三菱電機製エアコンの部分はネットで拾ってきたコードなので正確な所はよくわからない。

Qiitaで拾ってきた三菱電機製エアコン冷房27度 オンのコードらしい。
pigpioのirrp.py形式

1
{"ac:cool27": [3452, 1665, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449, 13342, 3452, 1665, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 1271, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 411, 449, 1271, 449, 1271, 449, 411, 449, 1271, 449, 411, 449, 1271, 449, 1271, 449]}

運転: 冷房27度のコード 最後を見てみると

  • MitsubishiElectric HVAC
  • Temperature 27
  • Mode Cool
  • Switch ON

となっているので解析できてる様な気がするね。

運転: 暖房22度のコード

1
850040001200320011003200120010001200110013000F00120032001100110012001100110032001200310013000F001200320011001100120011001300300013003000120011001100320013003000130010001100110013003000130010001200100013003000120011001300100013000F001200110012001100110011001200110012001100110011001200110013001000110011001300100012001000120011001300100011001100120011001300100011001100130030001200110012001000120011001200110011001100120031001300100013001000120010001200110012001000120032001100320011001100120011001200110011001100120011001200110011001100120011001200100012003100120032001200100012001100130010001100110012001100120010001200110013001000110032001100320013000F00130010001200110013000F00120011001200110013000F001200110012001100120010001200110012001100110011001200110012001000120011001300100013000F00120011001200110011001100120011001200110011001100120011001300100011001100120011001200100012001100130010001200100012001100120010001200110013003000120031001200110012001100120010001200110013000F001300100013001000120010001200110012001100110011001200110012001100110011001200110013001000120010001300100012001000120032001100320013000F00120031001300100013000F0012003100130010001200EE0184004200110032001300300012001100110011001200110012003100120010001200110013003000120031001300100012003100120011001100110012003100130030001300100012003100130030001200110013000F001200320011001100120011001100320013000F0013001000120011001100110013001000130010001100110012001100120011001100110013001000120011001100110012001100120010001200110012001100110011001200110012001000120031001200110012001100110011001200110013000F0012003200110011001300100013000F00120011001300100011003200120031001200110011001100120011001200110011001100130010001200100013001000120011001200310013003000120011001100110012001100120011001100110012001100120011001100110012003100120031001200110012001100110011001200110012001100120010001200110012001100110011001300100012001000120011001200110012001000120011001200110013000F00130010001200110011001100120011001200110011001100130010001200100012001100120011001100110013001000120011001100110012001100120010001200110012001100110032001200310012001100110011001300100013000F00120011001200110012001000120011001200110011001100120011001200110011001100120011001200110012001000120011001200100013001000120031001200310012001100110032001200100012001100120031001200110011004F03

運転: 暖房22度のコード

停止: 暖房22度のコード

1
850041001200310012003200110011001200110012001000130030001300100012001100110032001200310012001000120032001100110012001100120031001200310012001100110032001300300012001100110011001300300012001100120010001200310012001100120011001100110012001100120011001100110013001000120011001100110013001000120010001200110012001100120010001200110012001100110011001200110012001000120011001200110012001000120011001200110011001100130010001100320013000F00120011001200110013000F001300100012003100120031001200110012001100120010001300100013000F001300100013001000120010001200110013003000120031001300100012001000120011001200110012001000130010001200110011001100120031001200320013000F0012001100120011001100110012001100120010001200110013001000120010001200110013001000120010001300100012001100110011001300100013001000110011001200110012001000120011001300100013000F00120011001300100011001100120011001300100011001100120011001200110011001100130010001200100013001000120031001300300012001100120011001100110013001000130010001200100012001100120010001200110012001100110011001200110012001100110011001200110012001100110011001200110013000F0012003200120031001100110013003000120011001200310013000F00120011001200EE018400420011003200120031001200110012001000120011001100310013000F001200110012003100130030001200110013003000120011001100110012003100120031001300100012003100130030001200110012001000120031001300100012001100130030001200100012001100120011001200100012001100120011001100110012001100130010001200100012001100120010001300100013001000120010001200110012001000120011001200110012001000120011001200110011001100120011001300100013000F00130030001200110012001000120011001200110013000F001200310012003200110011001200110013001000120010001300100013000F00120011001200110013000F0013003000120032001200100012001100120010001200110012001100110011001200110012001000120032001300300012001000120011001200110012001000130010001200110013000F00120011001300100011001100120011001200110011001100120011001200100013001000130010001200100012001100120011001200100012001100130010001200100012001100130010001100110012001100120010001200110013001000110011001300100013000F001300100013003000120031001300100013001000110011001200110012001000120011001200110012001000120011001200110012001000130010001200110013000F001200110012001000120011001300100012001000120031001300310012001000130030001300100012003100130010001100110012004F03

停止: 暖房22度のコード

運転: 冷房26度のコード

1
8500410013003000130030001300100013000F0013001000130030001300100013000F00130030001300310012001000130030001300100013000F00130030001300310012001000130030001300310012001000130010001200310013000F0013001000130030001300100013000F00130010001300100013000F001300100013001000120010001300100013000F001300100014000F0013000F001300100013001000120010001300100013000F0013001000130010001200310013000F0013001000130010001200100013001000120031001300300013001000120010001300100013000F001300310012001000130030001300100013000F0014000F001300100013000F0013003000130031001200100013003000130030001300100013001000120010001300100013001000120010001300100013000F001300300013003100120010001300100013000F00130010001300100013000F00130010001300100013000F001300100013001000120010001300100013000F001300100014000F0013000F00130010001300100013000F0013001000130010001200100014000F0013001000120010001300100013001000120010001300100013000F001300100013000F0013001000130010001200310013000F0014000F0013001000120010001300100013001000120010001300100013000F00130010001300100013000F00130010001300100013000F0013001000130010001200100014000F0013000F00130030001300100013003000130010001200310013000F0013003000130010001300ED018500410012003100130030001300100012001000130010001300300013000F00130010001300300013003000130010001200310013000F00130010001300300014002F001300100013003000130030001300100013000F00130030001300100013000F0013003100120010001300100013000F00130010001300100013000F00130010001300100013000F001300100013001000120010001300100013000F001300100013001000120010001300100013000F0013001000130030001300100013000F00130010001300100013000F0014002F0013003100120010001300100013000F001300100014002F00130010001200310013000F001300100013001000120010001300100013003000130030001300100013003000130030001300100013000F001300100013001000120010001300100013001000120010001300300013003000130010001300100013000F001300100013001000120010001300100013000F00130010001300100013000F00130010001300100013000F001300100013001000120010001300100013001000120010001300100013000F00130010001300100013000F00130010001300100013000F001300100013001000120010001300100013000F001300100013000F0013003100120010001300100013001000120010001300100013000F00130010001300100013000F001300100013001000120010001300100013001000120010001300100013000F00130010001300100013000F0013003000130010001300300013000F001300300013001000130030001300100013004F03

運転: 冷房26度のコード

パナソニック製エアコンの信号解析

資料によると第1フレームは共通の信号で, 第2フレームに個別の信号の2フレーム構成で出来ているようだ。

第1フレームは8バイトの共通の信号。
この2つは同じ信号だよ。実際のソースコードを見てみると分かるはず。

b1b2b3b4b5b6b7b8bit order
0220e00400000006LSB first
4004072000000060MSB first

第2フレームはLSB firstビットオーダーの信号で19バイト。

  • 温度 7バイト目 (bit 1,2,3,4) + 16
  • 風向 9バイト目 (bit 0,1,2,3)
  • 風量 9バイト目 (bit 4,5,6,7)
  • 運転 6バイト目 (bit 4,5,6,7)
  • 電源 6バイト目 (bit 0)
  • CRC 19バイト目 (前の18バイトすべて足した値の mod 256)

ということ。

実際のリモコンで確かめる

運転: 暖房23度, 風向: 自動, 風量: 自動

運転: 暖房23度, 風向: 自動, 風量: 自動

1
840042000E0010000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000C0010000E0032000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E0030000E0010000E000E000E0032000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E000E0010000E000E0010000E0010000E000E000E0010000E0010000E000E0010000E000E0010000E0010000E000E000E0010000E0010000E000E0010000E000E0032000E0030000E0010000E000E0010000E000E0010000E0010000E007E01840040000E0010000E0032000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0032000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E0030000E0010000E0010000E0030000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E000E000E0010000E0010000E0010000E000E000E0032000E000E000E0010000E0032000E0030000E0030000E0010000E0032000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0030000E0032000E0030000E0032000E0030000E0010000E0030000E0010000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E000E0010000E000E0010000E0010000E000E000E0010000E0010000E000E000E0032000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0010000E000E000E0032000E008E02

運転: 暖房23度, 風向: 自動, 風量: 自動

運転: 冷房21度, 風向: 自動, 風量: 自動

運転: 冷房21度, 風向: 自動, 風量: 自動

1
860042000E000E000E0032000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0032000E0030000E0030000E0010000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E000E000E0010000E008001820042000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0010000C0010000E0010000E0010000E000E000E0010000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0030000E0032000E0010000E000E000E0032000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0030000E0010000E0010000E0010000E0030000E0030000E0010000E0010000E0010000E0030000E0010000E0030000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E0030000E0030000E0032000E0010000E0030000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0032000E0030000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E0030000E008E02

運転: 冷房21度, 風向: 自動, 風量: 自動

停止: 冷房16度, 風向: 水平, 風量: 最大

停止信号を送るとディスプレイが消えたのでこの写真は運転状態の写真です。

停止: 冷房16度, 風向: 水平, 風量: 最大

1
840042000E0010000E0030000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0032000E0030000E0032000E000E000E0010000E0030000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000C0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E0010000E000E000E008001820042000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0032000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0030000E0032000E0010000E000E000E0032000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000C0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E0030000E0030000E0010000E0010000E0010000E0030000E0030000E0032000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E0010000E000E000E0010000E0010000E0030000E0010000E0010000E000E000E0010000E0010000E000E000E0010000E0010000E000E000E0032000E0030000E0010000E0010000E000E000E0010000E0010000E0030000E0032000E000E000E0010000C0030000E0032000E0010000E000E000E008E02

停止: 冷房16度, 風向: 水平, 風量: 最大

以上で資料が正しいことを確認できた。

ダイキン工業製エアコンの信号解析

ページを分けました。
ダイキンエアコンのリモコン信号を解析する


解析対象のリモコン信号を入手

信号解析アプリケーションで解析するリモコン信号は以下の方法で手に入れてください。

パーサーがこれだから

赤外線リモコン信号の構造

アプリケーションノートより NEC, SIRC(SONY) フォーマット赤外線リモコン信号の構造を引用します。

NECフォーマット赤外線リモコン信号

Texas Instruments - Application Report SLAA644B
Infrared Remote Control Implementation With MSP430FR4xx1

より引用しました。

こんなインターネット上の文書より, SLAA644B1 アプリケーションノートを信用して欲しい。

1 Introduction

Figure1

中央の矢印で表現されている赤外線通信信号より左側が送信機, 右側が受信機。

5.1章で書かれている内容が NECフォーマット
この構造は家製協(AEHA)フォーマットも大筋で同じ

5.1 Pulse Distance Protocol

Pulse distance protocol is widely used by many appliances companies. It uses ASK modulation and pulse distance encoding with a carrier frequency of 38 kHz.

5.1.1 Frame Format

There are two kinds of frames in protocol: data frame and repeat frame.

A data frame consists of a leading code and data payload. The leading code is a burst with a length of 9 ms, followed by a pause of 4.5 ms. The data payload consists of 8 bits address to identify the device and 8 bits command for control words. Both are sent twice for reliability. The second transmission of address and command are complementary, therefore the total length of the data frame is constant (67.5 ms). The payload is finalized by a 560-µs carrier modulated tail pulse, to finish the last bit data gap.

Logic 1 is defined as a 560-µs carrier modulated period followed by a 1690-µs space period. Logic 0 is defined as a 560-µs carrier modulated period followed by a 560-µs space period (see Figure 12).

Figure12

A complete data frame format is shown in Figure 13.

Figure13

Repeat frame is defined to handle auto-repeat function and does not carry any address or command information. It includes train pulses and a tail pulse following (its format is shown in Figure 14). The repeat frame is repeated every 110 ms while the same key is still pressed.

Figure14

The full sequence format of pulse distance protocol is shown in Figure 15.

Figure15

SIRC(SONY)フォーマット赤外線リモコン信号

SLAA644B アプリケーションノートにはSIRCフォーマットが書かれていないので

Texas Instruments - Application Note AN091
RemoTI TM IR Signal Generation Application Note2

より引用しました。

3.4 SIRC Format

3.4.1 Modulation

The SIRC format uses a combination of the pulse distance and pulse width modulation technique. SIRC modulation is based on varying both the active period and duty cycle to distinguish between a logical “1” and a logical “0”. Note that the non-active period for both logical “1” and logical “0” is constant. Figure 7 below illustrates the principle of a combined pulse distance and pulse width modulation.

Figure7

SIRC format timing information:

  • Carrier: 40 kHz with 1/3 or 1/4 duty cycle
  • Active period:
    • Logical “0”: 600 µS
    • Logical “1”: 1.2 ms
  • Duty Cycle
    • Logical “0”: Active Period Logical “0” + 600 µS: 1.8 ms
    • Logical “1”: Active Period Logical “1” + 600 µS: 1.2 ms

3.4.2 Protocol

The SIRC protocol uses a 7-bit command and a 5-bit address length for each transfer. Other versions with different command and address length also exist. The command and address bits are preceded by a preamble with active period of 2.4 ms and duty cycle of 3 ms. The command and data bits are transferred with LSB first. See Figure 8 below for an illustration of the protocol.

Figure8

3.4.3 Repeat Sequence

The protocol described in Figure 8 is also used as the repeat pattern. This means initial frame and repeat frames are identical. The frame is repeated every 45 ms as long as the key remains pressed. See Figure 9 for an illustration of the repeat sequence.

Figure9

ざっと見ていると情報通信でおなじみの階層構造をなしているわけで。

NECフォーマットを例にして説明すると
送信機(リモコン)では情報(メッセージ)を Figure12 Pulse Distance Protocol (または Pulse Position Modulation) によってエンコードして

  • NECフォーマットを表すヘッダ(Leading Code)
    9ms ON / 4.5ms OFF
  • ペイロード(エンコードした情報)
  • トレイラー(ストップビット)

を一つのフレームに組み立てて物理層に送る。

Figure13

物理層(伝送路3)では(NECフォーマットの場合)このように見える。 Figure15

ついでに

物理層(伝送路3)では(SIRCフォーマットの場合)このように見える。 Figure9

こういうものを情報通信用語で「カプセル化」といいますね。

赤外線リモコン信号を解析する

送信機でカプセル化された通信であるから, 受信側プログラムはその反対「非カプセル化」を行なえば元の情報が得られる。

これは PureScript4 で書いたソースコードで
このプログラムの動作は上で図示されている構造を解体。つまり「非カプセル化」するということ。

このプログラムはこの3段階で解体するように作りました。

  • (物理層からやってきた)入力である複数のフレームを個々のフレームに切り離す。
  • 個々のフレームからペイロードを取り出す。
  • ペイロードをデコードする。

ここで

リモコンから送信された物理層のASK5変調信号(アプリケーションノート参照)は受信側の赤外線受信モジュールによって包絡線6が取り出されているので, このプログラムはそれを入力とします。

この図をよく見て欲しい。 Figure1

余談だけれど, 取り扱っているデータを

  • 「ビット」と言うのはふつう第1層(物理層)
  • 「フレーム」と言うのはふつう第2層(物理層の上層)

のお話ね。

以下簡単に説明します。

データ型宣言

Count型
38kHzの時間での何カウントになるか
1
2
3
4
5
6
7
8
newtype Count = Count Int
derive newtype instance eqCount               :: Eq Count
derive newtype instance ordCount              :: Ord Count
derive newtype instance showCount             :: Show Count
derive newtype instance semiringCount         :: Semiring Count
derive newtype instance ringCount             :: Ring Count
derive newtype instance commutativeRingCount  :: CommutativeRing Count
derive newtype instance eucideanRingCount     :: EuclideanRing Count

比較するためにクラス Eq, クラス Ord
表示するためにクラス Show
四則演算するためにクラス Semiring, クラス Ring, クラス CommutativeRing, クラス EuclideanRing
のインスタンスにしている。

Baseband型
パーサーからデコーダーに渡される中間表現であってON時間, OFF時間の配列。
1
2
3
4
5
6
type Pulse = {on :: Count, off :: Count}

-- |
newtype Baseband = Baseband (Array Pulse)
derive newtype instance eqBaseband    :: Eq Baseband
derive newtype instance showBaseband  :: Show Baseband
Bit型
1ビットを表現する。
  • 1 or 0
  • Hi or Lo
  • true or false
  • 付勢 or 消勢
  • Assert or Negate

これは2値を表せたら表現は何でもよいんですけどね。
赤外線リモコン信号回路には負論理信号が混ざるので, 今回はAssert/Negateでいきます。

1
2
3
data Bit
  = Negate
  | Assert
InfraredLeader
リーダー信号
AEHA | NEC | SIRC(SONY) | 不明のどれか
1
2
3
4
5
data InfraredLeader
  = LeaderAeha Pulse 
  | LeaderNec Pulse 
  | LeaderSirc Pulse 
  | LeaderUnknown Pulse 

InfraredLeader のコンストラクタ

ON時間 / OFF時間からリーダー信号を得る
それぞれの判定に使う時間は参考リンクにある通りで, 許容誤差の上限下限を 0.2ms にしておいた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
makeInfraredLeader :: Pulse -> InfraredLeader 
makeInfraredLeader = case _ of
  p | aeha p    -> LeaderAeha p
    | nec p     -> LeaderNec p
    | sirc p    -> LeaderSirc p
    | otherwise -> LeaderUnknown p
  where

  -- | upper lower tolerance 0.2ms
  typical = withTolerance {upper: Milliseconds 0.2, lower: Milliseconds 0.2}

  -- | H-level width, typical 3.4ms
  -- | L-level width, typical 1.7ms
  aeha :: Pulse -> Boolean
  aeha pulse =
    let on_   = typical (Milliseconds 3.4)
        off_  = typical (Milliseconds 1.7)
    in
    (Array.any (_ == pulse.on) on_) && (Array.any (_ == pulse.off) off_)

  -- | H-level width, typical 9.0ms
  -- | L-level width, typical 4.5ms
  nec :: Pulse -> Boolean
  nec pulse =
    let on_   = typical (Milliseconds 9.0)
        off_  = typical (Milliseconds 4.5)
    in
    (Array.any (_ == pulse.on) on_) && (Array.any (_ == pulse.off) off_)

  -- | H-level width, typical 2.4ms
  -- | L-level width, typical 0.6ms
  sirc :: Pulse -> Boolean
  sirc pulse =
    let on_   = typical (Milliseconds 2.4)
        off_  = typical (Milliseconds 0.6)
    in
    (Array.any (_ == pulse.on) on_) && (Array.any (_ == pulse.off) off_)
InfraredCode
赤外線リモコン信号。
正しい入力なら, 最後はこれに変換される。
1
2
3
4
5
data InfraredCode
  = Unknown (Array Bit)
  | AEHA {custom :: LsbFirst, parity :: LsbFirst, data0 :: LsbFirst, data :: Array LsbFirst, stop :: Bit}
  | NEC  {custom :: LsbFirst, data :: LsbFirst, invData :: LsbFirst, stop :: Bit}
  | SIRC {command :: LsbFirst, address :: LsbFirst}

入力文字列のパーサー

参考リンクのADRSIR文書情報で定義された入力をデコーダーに渡す中間表現に変換する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
infraredHexStringParser:: Parser InfraredHexString Baseband
infraredHexStringParser = do
    arr <- Array.some (pulse <* skipSpaces)
    eof
    pure (Baseband arr)
  where

  pulse = do
    -- 入力値はon -> offの順番
    ton <- valueOf32Bit <?> "on-counts"
    toff <- valueOf32Bit <?> "off-counts"
    pure {on: Count ton, off: Count toff}

  valueOf32Bit = do
    -- 入力値はLower -> Higherの順番
    lower <- hexd16bit <?> "lower-pair hex digit"
    higher<- hexd16bit <?> "higher-pair hex digit"
    -- ここは普通の数字の書き方(位取り記数法: 高位が前, 下位が後)
    let str = higher <> lower
        maybeNum = Int.fromStringAs Int.hexadecimal str
    -- 入力値は検査済みなのでfromJustでよい
    pure (unsafePartial $ fromJust maybeNum)

  hexd16bit = do
    a <- hexDigit
    b <- hexDigit
    pure $ fromCharArray [ a, b ]

デコーダー

中間表現から赤外線リモコン信号に変換する。
書いてある通りに Phase1, Phase2, Phase3 の3段階で砕く。

«<7 は関数合成演算子でHaskellの . ドット
traverse8 は Haskellの mapM
<=<9 はモナドの合成演算子でHaskellと同じ

1
2
3
decodeBaseband :: Baseband -> Either ProcessError (Array InfraredCode)
decodeBaseband =
  traverse (decodePhase3 <=< decodePhase2) <<< decodePhase1 

デコード第1段階

リモコンから複数フレームが送られる事があるので, 1段目は入力を各フレームに変換する。
OFF時間 8ms継続でフレームを切り離す。
8msは参考リンクにある通りAEHA規格が根拠であるが, SIRC(SONY)規格でも有効。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
decodePhase1 :: Baseband -> Array (Array Pulse)
decodePhase1 (Baseband bb) =
  unfoldr1 chop bb
  where

  chop :: Array Pulse -> Tuple (Array Pulse) (Maybe (Array Pulse))
  chop xs = 
    case frames xs of
      { init: a, rest: [] } -> Tuple a Nothing
      { init: a, rest: b_ } -> Tuple a (Just b_)

  frames :: Array Pulse -> {init :: Array Pulse, rest :: Array Pulse}
  frames pulses = 
    let sep = Array.span (\count -> count.off < threshold) pulses
    in
    { init: sep.init <> Array.take 1 sep.rest
    , rest: Array.drop 1 sep.rest
    }

  threshold :: Count
  threshold = fromMilliseconds (Milliseconds 8.0)

デコード第2段階

入力フレームをリーダ部とビット配列にする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
decodePhase2 :: Array Pulse -> Either ProcessError (Tuple InfraredLeader (Array Bit))
decodePhase2 tokens =
  case Array.uncons tokens of
    Just {head: x, tail: xs} ->
      let leader = makeInfraredLeader x
      in
      Right $ Tuple leader (demodulate leader xs)

    Nothing ->
      Left "Unexpected end of input"

デコード第3段階

AEHA / NEC / SIRC / 不明 それぞれ対応する関数で変換する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
decodePhase3 :: Tuple InfraredLeader (Array Bit) -> Either ProcessError InfraredCode
decodePhase3 (Tuple leader bitarray) =
  evalState (runExceptT decoder) bitarray
  where

  decoder :: DecodeMonad ProcessError InfraredCode
  decoder = case leader of
    LeaderAeha _    -> decodeAeha
    LeaderNec _     -> decodeNec
    LeaderSirc _    -> decodeSirc
    LeaderUnknown _ -> decodeUnknown

デコード用モナド

1
type DecodeMonad e a = ExceptT e (State (Array Bit)) a
  • 3段階のデコード関数はEitherモナド
  • 破壊的更新(変数再代入)を伴う操作を行なうためのStateモナド

この2つのモナドを合成している。

1
State (Array Bit)

ここに書かれているとおりStateモナドのフィールドがビットの配列となっているので,

  • 取得 (State.get)
  • 更新 (State.put)

などのアクセッサーを通じてフィールドにアクセスすることになる。
この部分は他言語でいう Getter / Setter のこと。

Haskell / PureScript のようないわゆる純粋関数型言語では変数の取り扱いに特別の配慮が必要なんでこんなことになる。
純粋にこだわらない言語ではこんなめんどくさい事をしなくても普通に変数を使えばよいですね。

1ビットを取り出す

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
takeBit :: ProcessError -> DecodeMonad ProcessError Bit
takeBit errmsg = do
  state <- State.get
  case Array.uncons state of
    Nothing ->
      throwError errmsg

    Just x -> do
      State.put x.tail
      pure x.head

ここでの動作は State.getでビット配列を取得して 取得したビット配列が

  • 空ならエラーメッセージを返却
  • 有れば1ビットを取り出して, 残りを次のStateとして更新, 取り出した1ビットを返却

nビットを取り出す

1
2
3
4
5
6
7
8
9
takeBits :: Int -> ProcessError -> DecodeMonad ProcessError (NonEmptyArray Bit)
takeBits n errmsg = do
  state <- State.get
  let bitarray  = Array.take n state
      nextState = Array.drop n state
  State.put nextState
  maybe (throwError errmsg) pure do
    guard (Array.length bitarray == n)
    NEA.fromArray bitarray

残りのビットをすべて取り出す

1
2
3
4
5
takeEnd :: ProcessError -> DecodeMonad ProcessError (NonEmptyArray Bit)
takeEnd errmsg = do
  array <- State.get
  State.put []
  maybe (throwError errmsg) pure $ NEA.fromArray array

デコード (AEHA)

先頭から以下の順番で取り出す

  1. カスタムコード(16ビットlsb first)
  2. パリティ(4ビット lsb first)
  3. data0(4ビット lsb first)
  4. dataN(8ビット lsb first)
  5. ストップビット
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
aehaProtocol :: DecodeMonad ProcessError InfraredCode
aehaProtocol = do
  custom <- takeBits 16 "fail to read: custom code (AEHA)"
  parity <- takeBits 4 "fail to read: parity (AEHA)"
  data_0 <- takeBits 4 "fail to read: data0 (AEHA)"
  data_N <- takeEnd "fail to read: data (AEHA)"
  let init = NEA.init data_N
      last = NEA.last data_N
      octets = toArrayNonEmptyArray 8 init
  pure $ AEHA { custom: LsbFirst custom
              , parity: LsbFirst parity
              , data0: LsbFirst data_0
              , data: map LsbFirst octets
              , stop: last
              }

デコード(NEC)

先頭から以下の順番で取り出す

  1. カスタムコード(16ビットlsb first)
  2. data(8ビット lsb first)
  3. dataの反転(8ビット lsb first)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
necProtocol :: DecodeMonad ProcessError InfraredCode
necProtocol = do
  custom <- takeBits 16 "fail to read: custom code (NEC)"
  data__ <- takeBits 8 "fail to read: data (NEC)"
  i_data <- takeBits 8 "fail to read: inv-data (NEC)"
  stopbt <- takeBit "fail to read: stop bit (NEC)"
  pure $ NEC  { custom: LsbFirst custom
              , data: LsbFirst data__
              , invData: LsbFirst i_data
              , stop: stopbt
              }

デコード(SIRC)

先頭から以下の順番で取り出す

  1. コマンド(7ビットlsb first)
  2. アドレス(5/8/13ビット lsb first)
  3. ストップビット
1
2
3
4
5
6
7
sircProtocol :: DecodeMonad ProcessError InfraredCode
sircProtocol = do
  comm <- takeBits 7 "fail to read: command code (SIRC)"
  addr <- takeEnd "fail to read: address (SIRC)"
  pure $ SIRC { command: LsbFirst comm
              , address: LsbFirst addr
              }

赤外線リモコン信号の解析はこれでいいと思う。

さいごに

  • 入力文字列
  • カウントの配列
  • フレームの配列
  • ビット列
  • 赤外線リモコン信号

入力文字列が各段階で変換される様子を見ているのが上のページ。

フロントエンドはPureScriptで書いていますが, このプログラムの動作はまさにコレ

プログラミングはデータの変換をするものだ

—プログラミングElixir 第1.1章

参考文献

赤外線リモコン誕生記 - 特許を取れなかった技術,取らなかった技術

赤外線信号

ADRSIR

メーカーによるソフトウェアダウンロードページから

http://bit-trade-one.co.jp/support/download/

I2C仕様文書, リモコンコードCSVファイルをダウンロードする。

リモコンデーターの構造

1 赤外線データのON-OFF-ON-OFF…の時間を38kHzの時間での何カウントになるかを求めます
3.2msの場合は、3.2ms / 0.026ms(38kHz) = 123 (0x7B)

2 各カウント値を2バイトデータとして先頭からカウント値のLoバイト、Hiバイトの順番にデータを送信します

データ
送信データ長は、ON-OFFで1データとなります。よって、この場合のデータ長は8となります。
0x7B, 0x00, 0x3D, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x2E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x2E, 0x00, 0x0F, 0x00, 0x2E, 0x00, 0x0F, 0x00, 0x0F, 0x00

ADRSIR I2C仕様文書

comments powered by Disqus

This website uses cookies to improve your experience.
このサイトは「Googleアナリティクス」を使用しています。
Googleアナリティクスはデータの収集のためにCookieを使用しています。


Built with Hugo
テーマ StackJimmy によって設計されています。