赤外線リモコンをラズベリーパイで代用


二番煎じですが、エアコンの赤外線リモコンをラズベリーパイで代用させてみました。
それと前から欲しかったオシロスコープが５万円代に成っていたので購入して波形の確認を行ってみました。最初操作に戸惑いましたが１日くらいいじっていたら良い感じの波形を取れるように成りました。


アマゾンで購入したIRモジュール（回路図が入っていなかったので電圧がわからないのですが、ひとまずラズベリーパイの５Vに繋げます。発信器はラズベリーパイから５Vだと弱い気がします。安定化電源で５Vを入力すると動作しました。ただ、後から購入した裸の赤外線LEDだけの方が良いみたいです）

![ir.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015539/bf394b7f-3b92-d70f-117a-294f078e70c2.png)


### 参考ページ

赤外線リモコンのフォーマット
http://elm-chan.org/docs/ir_format.html

下のコードを元に家製協(AEHA)フォーマットにカスタムしています。
http://abyz.me.uk/rpi/pigpio/examples.html#Python_irrp_py

### 受信機のPythonコード

コールバック関数で受信機のパルスのON・OFF時にパルスの幅を取得して、最後に425usで長さを割りプリント出力しています。
出力コードを送信機で処理します。
pi.set_watchdogで受信中か確認して、一定期間受信してなければTimeOutを発生させて処理を終える。

```python:ir_read.py
import time
import pigpio

IR_READ_PIN = 26

pi = pigpio.pi()
if not pi.connected:
    print(f"cannot connect to pigpio")
    exit()

pi.set_mode(LED_PIN, pigpio.OUTPUT)
pi.set_mode(IR_READ_PIN, pigpio.INPUT)
pi.set_glitch_filter(IR_READ_PIN, 100)


last_tick = 0
in_code = False
code = []
fetching_code = False
PRE_MS = 300
POST_MS = 20
POST_US = POST_MS * 1000
PRE_US = PRE_MS * 1000
SHORT = 20
T_US = 425


def end_of_code():
    global code, fetching_code
    entries = len(code) - 1
    if entries > SHORT:
        print("entries", entries)
        code = [round(c / T_US) for c in code]
        print("code", code)
        fetching_code = False
    else:
        code = []
        print("Short code, probably a repeat, try again")


def cbf(gpio, level, tick):
    global last_tick, in_code, code, fetching_code

    if level != pigpio.TIMEOUT:

        edge = pigpio.tickDiff(last_tick, tick)
        last_tick = tick

        if fetching_code:
            #print(gpio, level, tick, edge)

            if (edge > PRE_US) and (not in_code):  # Start of a code.
                in_code = True
                pi.set_watchdog(gpio, POST_MS)  # Start watchdog.
                #print("set_watchdog", gpio, POST_MS)

            elif (edge > POST_US) and in_code:  # End of a code.
                in_code = False
                pi.set_watchdog(gpio, 0)  # Cancel watchdog.
                end_of_code()

            elif in_code:
                code.append(edge)

    else:
        # print("timeout")
        pi.set_watchdog(gpio, 0)  # Cancel watchdog.
        if in_code:
            in_code = False
            end_of_code()


cb1 = pi.callback(IR_READ_PIN, pigpio.EITHER_EDGE, cbf)

try:

    while True:
        code = []
        fetching_code = True
        while fetching_code:
            time.sleep(0.1)
        print("Okay")
        time.sleep(0.5)


except KeyboardInterrupt:
    print(f"cancelling")


cb1.cancel()
pi.stop()
```

### 送信機のPythonコード

受信機で取得したコードを赤外線で送ります。
carrier関数ないで425us内にデューティ1/3、３８KHzのパルスを作成してます。
（試しに３８KHzのパルスを単にONのままにして送信したみましたが、それだと受信しないものですね）
write_command関数ないで赤外線のコード処理を行っています。配列の先頭をIndex:0として、偶数をON、奇数をOFFとしてパルスを発生させています。


```python:ir_write.py
import time
import pigpio

IR_WRITE_PIN = 26
FREQ = 38.0
T_US = 425
GAP_MS = 100
GAP_S = GAP_MS / 1000.0

AIRCON_OFF = [8, 4, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1,
              1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1]

AIRCON_ON = [8, 4, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1,
             1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1]

TEST_PULSE = AIRCON_ON  # AIRCON_ON  # [1, 1, 1]


def carrier(gpio, frequency, micros):
    """
    Generate carrier square wave.
    """
    wf = []
    cycle = 1000.0 / frequency
    cycles = int(round(micros/cycle))
    on = int(round(cycle / 3.0))
    sofar = 0
    for c in range(cycles):
        target = int(round((c+1)*cycle))
        sofar += on
        off = target - sofar
        sofar += off
        wf.append(pigpio.pulse(1 << gpio, 0, on))
        wf.append(pigpio.pulse(0, 1 << gpio, off))
    return wf


def write_command():
    pi.wave_add_new()
    # Create wave
    emit_time = time.time()

    marks_wid = {}
    spaces_wid = {}

    code = TEST_PULSE
    wave = []

    for i, ci in enumerate(code):
        pulse_len = ci * T_US
        if i & 1:  # Space
            if True or ci not in spaces_wid:
                pi.wave_add_generic([pigpio.pulse(0, 0, pulse_len)])
                spaces_wid[ci] = pi.wave_create()
            wave.append(spaces_wid[ci])
        else:  # Mark
            if ci not in marks_wid:
                wf = carrier(IR_WRITE_PIN, FREQ, pulse_len)
                pi.wave_add_generic(wf)
                marks_wid[ci] = pi.wave_create()
            wave.append(marks_wid[ci])

    delay = emit_time - time.time()

    if delay > 0.0:
        time.sleep(delay)

    pi.wave_chain(wave)

    while pi.wave_tx_busy():
        time.sleep(0.002)

    emit_time = time.time() + GAP_S

    for i in marks_wid:
        pi.wave_delete(marks_wid[i])
    for i in spaces_wid:
        pi.wave_delete(spaces_wid[i])
    pi.wave_clear()


pi = pigpio.pi()
if not pi.connected:
    print(f"cannot connect to pigpio")
    exit()

pi.set_mode(LED_PIN, pigpio.OUTPUT)
pi.set_mode(IR_WRITE_PIN, pigpio.OUTPUT)

try:
    write_command()
except Exception as e:
    print(f"Exception {e}")


pi.stop()

```

### 送信機の出力結果

家電の赤外線受信機に向けて、ラズベリーパイの送信で赤外線コマンドを発射すると操作可能なことを確認出来ました。

#### パルスの計測

水平軸の単位を20usとして周期が26us、ONが1/3くらいに成っているので、carrier関数の生成に成功しています。

![オシロ３８k.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015539/bc1d7c3f-8cb4-0c28-28c2-bdeb3abd95b5.png)

#### コマンドの計測
先頭のパルスが[8, 4, 1, 3, 1, でOn:8t,Off:4t, On:1t, 0ff:3t, On:1tと続いているので正しい動作と成っています。

![オシロIR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015539/c545d93c-2bd6-c1df-247e-1a84ce2dd38b.png)