Raspberry Pi 3 + LIRC + Pythonでテレビのon/off

## 背景

Raspberry PiからテレビのOn/Offを赤外線で制御するには[LIRC](http://www.lirc.org/)というライブラリが便利そうだということがわかりました。

しかし、LIRC向けのPythonライブラリで信号を出せるものがどうにも見つかりません（検索上位に出る[python-lirc](https://pypi.org/project/python-lirc/)は受信＝学習側のようです）。そこで念のためにLIRCのコードを見てみると、0.10.1 からPythonライブラリが同梱されていることに気づきました。

そこでこの記事ではLIRCに同梱されたPythonライブラリを使ってテレビをOn/Offするサンプルを作成します。

## 環境

+ Raspberry Pi Model B V1.2
+ 2018-11-13-raspbian-stretch-lite
+ 赤外線LED [OSI5FU5111C-40](http://akizukidenshi.com/catalog/g/gI-03261/)
+ 200ohm 抵抗


## LIRCのインストール
以下のコマンドでLIRCはインストールされますが、デフォルト環境では0.9.4c-9がインストールされてしまうため、Pythonのライブラリが入りません。

```bash
pi@raspberrypi:~$ sudo apt-get install lirc
```

そこで `/etc/apt/sources.list`を以下のように変更し、testingリポジトリを参照できるようにします。


```conf

deb http://raspbian.raspberrypi.org/raspbian/ stretch main contrib non-free rpi
deb http://raspbian.raspberrypi.org/raspbian/ testing main contrib non-free rpi #追加
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
#deb-src http://raspbian.raspberrypi.org/raspbian/ stretch main contrib non-free rpi                                                                                 
```

そしてお約束のアップデートをかけます。

```bash
pi@raspberrypi:~$ sudo apt-get update
```

インストール。0.10.1-5がインストールされます。

```bash
pi@raspberrypi:~$ sudo apt-get install lirc
```

念のためにPythonライブラリが入っていることも確認しましょう。

```bash
pi@raspberrypi:~$ tree /usr/lib/arm-linux-gnueabihf/python3.7/site-packages/lirc
/usr/lib/arm-linux-gnueabihf/python3.7/site-packages/lirc
├── async_client.py
├── client.py
├── _client.so
├── config.py
├── database.py
├── __init__.py
└── paths.py
```

## Python3.7のインストール

インストールされたライブラリがPython3.7用となっているのでPython3.7をインストールします。

```bash
sudo apt-get install python3.7
```

## LIRCの設定

[こちらのサイト](https://qiita.com/nomunomu0504/items/8f47d97766c69a6bd6b1)を参考に設定を行います。感謝。


```bash:/boot/config.txt
# Uncomment this to enable the lirc-rpi module
dtoverlay=lirc-rpi
dtparam=gpio_out_pin=25
dtparam=gpio_in_pin=24
```

```bash:/etc/lirc/lirc_options.conf
[lircd]
nodaemon        = False
#driver          = devinput
driver          = default
#device          = auto
device          = /dev/lirc0
```

再起動しておきましょう。以下のドライバがロードされていればOKなようです。

```
pi@raspberrypi:~$ lsmod | grep lirc
lirc_rpi               16384  2
lirc_dev               16384  1 lirc_rpi
```


また、制御したい機器に合わせて`/etc/lirc/lircd.conf.d`に設定ファイルを置きます。[ここ](http://lirc.sourceforge.net/remotes/)に大量の設定ファイルがあるので、該当するものがあればそのまま使えると思います。

私の場合はSONYのRM-JD030というリモコンですが、なかったので[SONYのテレビ一般用っぽいファイル](http://lirc.sourceforge.net/remotes/sony/TV)をコピーし、以下のような設定にしました。

```conf:/etc/lirc/lircd.conf.d/sony.tv.conf
begin remote

  name  SONY-TV
  bits            7
  flags SPACE_ENC|CONST_LENGTH
  eps            30
  aeps          100

  header       2574   444
  one          1246   557
  zero          625   557
  ptrail        580
  post_data_bits  4
  post_data      0x8
  gap          45239
  toggle_bit      0
  min_repeat      2


      begin codes
          POWER                    0x54
      end codes

end remote
```

設定ファイルを配置したらデーモンを再起動します。

```bash
pi@raspberrypi:~$ sudo /etc/init.d/lircd  restart
```

設定が反映されていることを確認します。

```bash
pi@raspberrypi:~$ irsend LIST "" ""

SONY-TV
devinput-32
devinput-64
```

以下のコマンドで試し打ちします。

```bash
pi@raspberrypi:~$ irsend SEND_ONCE SONY-TV POWER
```


## Pythonからの制御

同梱のテストが参考になります。公式サイトの[browse the repository](http://lirc.git.sourceforge.net/git/gitweb.cgi?p=lirc/lirc;a=tree)はリンクが切れているようなので、手元にクローンして確認します。

```bash
pi@raspberrypi:~$ git clone git://git.code.sf.net/p/lirc/git lirc
pi@raspberrypi:~$ cd lirc
pi@raspberrypi:~$ vi python-pkg/tests/test_client.py
```

```python:python-pkg/tests/test_client.py
137     def testRemotesCommmand(self):
138         ''' Do LIST without arguments . '''
139
140         if os.path.exists(_SOCKET):
141             os.unlink(_SOCKET)
142         cmd = [_SOCAT, 'UNIX-LISTEN:' + _SOCKET,
143                'EXEC:"%s ./dummy-server 100"' % _EXPECT]
144         with subprocess.Popen(cmd,
145                               stdout = subprocess.PIPE,
146                               stderr = subprocess.STDOUT) as child:
147             _wait_for_socket()
148             with CommandConnection(socket_path=_SOCKET) as conn:
149                 reply = lirc.ListRemotesCommand(conn).run()
150             self.assertEqual(len(reply.data), 2)
151             self.assertEqual(reply.success, True)
152             self.assertEqual(reply.data[0], 'mceusb1')
153             self.assertEqual(reply.data[1], 'mceusb2')
154             self.assertEqual(reply.sighup, False)
```

どうやら`CommandConnection`クラスに`socket_path`を設定してインスタンスを作成。さらに、`Command`クラスの各種サブクラスのインスタンスを作成して`run()`を呼べばよさそうです。

また、`socket_path`は以下の設定ファイルに記述されています。

```bash:/etc/lirc/lirc_options.conf
[lircd]
# 中略
output          = /var/run/lirc/lircd
```


テストでは`ListRemotesCommand`クラス使用していますが(`irsend LIST "" ""`と同様の挙動)、コマンドの送信には`SendCommand`クラスが使えそうです。

ちなみに、実装は以下の個所となります。

```python:python-pkg/lirc/client.py
598 class SendCommand(Command):
599     ''' Send given key, see SEND_ONCE in lircd(8) manpage. '''
600
601     def __init__(self, connection: AbstractConnection,
602                  remote: str, keys: str):
603         if not len(keys):
604             raise ValueError('No keys to send given')
605         cmd = 'SEND_ONCE %s %s\n' % (remote, ' '.join(keys))
606         Command.__init__(self, cmd, connection)
```

以上より、このようなサンプルとなりました。

```python:sample.py
import lirc

conn = lirc.CommandConnection(socket_path='/var/run/lirc/lircd')
lirc.SendCommand(conn, remote='SONY-TV', keys=['POWER']).run()
```

`irsend SEND_ONCE SONY-TV POWER`と一致した呼び出しで分かりやすいですね。