Intel Edisonで赤外線リモコンの受信

Intel Edison Arduino BoardのGPIOを利用し、赤外線リモコンの受信を行う。今回赤外線リモコン受信モジュールは[PL-IRM0101](http://akizukidenshi.com/download/ds/paralight/DS-27-01-0001%20PL-IRM0101-3%20B1.pdf)を用いた。

# 赤外線センサーの接続

ブレッドボードを使いIntel Edison Arduino Boardと`PL-IRM0101`を接続する。

`PL-IRM0101` のピンは向かって左側から **Vout**（4.5〜5.5V）、 **GND** 、 **VCC** （4.5〜5.5V）となっているため、左側のピンをデジタル入出力8へ、真ん中のピンをGNDへ、右側のピンをPower 5Vへ接続する。

![irsensor.png](https://qiita-image-store.s3.amazonaws.com/0/37648/74f79f92-144e-0805-eff1-33204c8c6d61.png)

![DSC_0219.JPG](https://qiita-image-store.s3.amazonaws.com/0/37648/dcb61dd5-b602-50d3-1bbc-296e39894335.jpeg)

![DSC_0220.JPG](https://qiita-image-store.s3.amazonaws.com/0/37648/2e0e66e3-e7b4-e7fb-cdd1-0a1456282917.jpeg)

# 赤外線リモコンのフォーマット

赤外線リモコンのフォーマットはRFP等で明確に定められているものではないが、国内ではおおよそNECフォーマットか家電協フォーマットに準拠しているようである。各々のフォーマットの詳細は、次の資料が参考になる。

http://akizukidenshi.com/download/k4174_format.pdf

今回はpython2.7で家電協フォーマットで送られたコマンドの解析部分を実装する。

# 赤外線リモコンのコマンド解析プログラム

Intel EdisonのデフォルトOSには、Intelが提供するハードウェア抽象化ライブラリ **MRAA** が最初から搭載されており、Pythonからも標準ライブラリであるかのように利用することができる。

```python:irsensor.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import mraa
import time

PIN = 8          # Voutを接続したArudino BoardのGPIO番号
BUF = 0.35       # HIGH/LOW継続時間の許容ブレ幅 [ms]
FLAME_SPACE = 50 # 一連のコマンドが終了したと判断するLOWが継続する時間 [ms]

HIGH, LOW = 1, 0


class KadenkyoDecoder(object):
    L_HIGH = 3.2   # 家電協フォーマットのリーダーコードにおけるHIGH継続時間 [ms]
    L_LOW = 1.6    # 家電協フォーマットのリーダーコードにおけるLOW継続時間 [ms]

    D_LOW_0 = 0.4  # 家電協フォーマットのデータコードにおいて、0の場合のLOW継続時間 [ms]
    D_LOW_1 = 1.2  # 家電協フォーマットのデータコードにおいて、1の場合のLOW継続時間 [ms]

    def __init__(self, code):
        self.code = code

    def decode(self):
        if len(self.code) <= 3: # 受信したHIGH/LOWリストのサイズが3以下の場合はノイズとみなす
            return "UNKNOWN"

        paired = self.__make_pair(self.code[:-1])

        if not self.__is_valid_format(paired[0]):
            return "UNKNOWN"
        else:
            return "".join(map(str, map(self.__make_bit, paired[1:])))

    def __make_pair(self, code): # HIGH/LOW継続時間のリストをHIGH & LOWのペアのリストに変換する
        def pair(l):
            for i in xrange(0, len(l), 2):
                yield {"H": l[i]["len"], "L": l[i + 1]["len"]}
        return list(pair(code))

    def __is_valid_format(self, lc): # 家電協フォーマットのリーダーコードに合致するかチェック
        h = self.L_HIGH - BUF <= lc["H"] <= self.L_HIGH + BUF
        l = self.L_LOW - BUF <= lc["L"] <= self.L_LOW + BUF
        return h and l

    def __make_bit(self, dc): # 家電協フォーマットのデータコードの定義に従い、HIGH & LOWのペアを0 or 1に変換する
        if self.D_LOW_0 - BUF <= dc["L"] <= self.D_LOW_0 + BUF:
            return 0
        elif self.D_LOW_1 - BUF <= dc["L"] <= self.D_LOW_1 + BUF:
            return 1
        else:
            return 9


def print_code(code):
    for i, elem in enumerate(code):
        s = "LOW " if elem["state"] == HIGH else "HIGH"
        print "%03d: %s %5.2f[ms]" % (i, s, round(elem["len"], 2))


def main():
    ir = mraa.Gpio(PIN) # IntelのMRAAライブラリを用いて指定したGPIOの電圧状態を取得する
    ir.dir(mraa.DIR_IN)

    recv, last, st, nt, code = False, LOW, 0, 0, []
    while True:
        current = not ir.read()  # PL-IRM0101は赤外線を受信した場合に0を返すのでビット反転
        nt = time.time() * 1000
        if last != current: # HIGHとLOWが反転したエッジからエッジの時間を計測する
            last = current
            if not recv:
                recv = True
                st = time.time() * 1000
                continue
            code.append({"state": last, "len": nt - st})
            st = nt
        if recv and last == LOW and nt - st > FLAME_SPACE: # FLAME_SPACEの間継続してLOWの場合、コマンドが終了したと判断する
            # print_code(code)
            s = KadenkyoDecoder(code).decode()
            print s
            recv, last, st, nt, code = False, LOW, 0, 0, []

if __name__ == "__main__":
    try:
        print "start"
        main()
    except KeyboardInterrupt as err: # Ctrl-Cが入力されたら終了
        print "end"
```

上記のコードでは、ざっくり以下のような処理をしている（エラー処理はまったく実装していないので、必要に応じて追加する方が良い）。

1. main() : 赤外線リモコン受信モジュールのVoutの電圧変化から、赤外線を受信している（HIGH）の時間と受信していない（LOW）の時間を計測してリスト化
2. KadenkyoDecoder(code).decode() : 家電協フォーマットの定義に照らしあわせて、得られた赤外線パルスをリーダーコード・データコード・ストップビットに分解
3. 家電協フォーマットに従っていた場合、データコード部分を48ビットのビット配列にデコードして返す
4. 家電協フォーマットに従っていない場合、"UNKNOWN"を返す

> `PL-IRM0101` から得られるVoutは、赤外線を受信している時に0、赤外線を受信していない時に1が返るので注意

PIN番号やBUF秒数、FLAME_SPACE秒数は、環境によって異なると思われる。イイカンジに調整してほしい。

# 実行結果

```text:実行結果
root@edison:~# ./irsensor.py 
start
010000000000010000000001000000001011110010111101
UNKNOWN
UNKNOWN
010000000000010000000001000000000010110000101101
UNKNOWN
UNKNOWN
010000000000010000000001000000001010110010101101
UNKNOWN
UNKNOWN
UNKNOWN
UNKNOWN
010000000000010000000001000000001010110010101101
UNKNOWN
UNKNOWN
010000000000010000000001000000001010000010100001
UNKNOWN
UNKNOWN
UNKNOWN
UNKNOWN
010000000000010000000001000000001011110010111101
UNKNOWN
UNKNOWN
^Cend
```

とあるセットトップボックス用の赤外線リモコンをセンサーに向け、いくつかのボタンを押してみたところ、上記のような結果になった。

このセットトップボックス用赤外線リモコンでは、テレビ自身を操作する家電協フォーマットに従った4種のコマンドと、セットトップボックスを操作する家電協フォーマットに従わないの複数のコマンドが送信できるようである。

```text:送信されたコマンドとテレビ操作用ボタンの対応
010000000000010000000001000000001011110010111101  => 電源ON/OFF
010000000000010000000001000000000010110000101101  => チャンネル上
010000000000010000000001000000001010110010101101  => チャンネル下
010000000000010000000001000000001010000010100001  => 入力ソース切替
```

# まとめ

今回はntel Edison Arduino Boardと赤外線リモコン受信モジュールを用いて、赤外線リモコンの家電協フォーマットのコマンドを解析した。

今後はNECフォーマットへの対応や、ボタンを押しっぱなしにした場合への対応、また赤外線リモコンのコマンド学習機能などに対応すると面白いだろう。