Raspberry Pi と python-cec で、HDMI CEC を経由してテレビの電源ON/OFFや音量調整を行う

先日Google Nest miniをお迎えしたので、家のテレビを操作してみようと思ったところ、手元のテレビでは直接の操作に対応していませんでした。

Google Nest mini以外でもテレビを操作する方法がないかを調べたところ、HDMI CECを使えばいけそうでした。

 
手元にある道具では、Raspberry PiとテレビをHDMIで接続し、libceccec-client を使えば良さそうでした。
Raspberry Pi のcec 制御とHDMIのオンオフ - それマグで!

 
せっかくなのでPythonでlibcecを直接扱う方法がないかを調べたところ python-cec がありました。READMEには「libcec bindings for Python」と書かれていました。
trainman419/python-cec

最新リリースは 2018/11/9 でしたが、Github上では今年もcommitされていたため、試してみることにしました。

なお、久しぶりにRaspberry Piをさわるため、セットアップするところからメモに残します。

 
目次

 

環境

開発はWindows上で、実行はRaspberry Piで行います。

Raspberry Pi

 

Windows 10

 

テレビ

 

ネットワーク構成図

Windows10 - Raspberry Pi間は有線LAN、Raspberry Pi - テレビ間はHDMIとします。

-----------------------------------------
Windows10
[IPアドレス:DHCP (192.168.0.xxx)]
-----------------------------------------
|
(LANケーブル)
|
-----------------------------------------
スイッチングハブ
-----------------------------------------
|
(LANケーブル)
|
-----------------------------------------
(`eth0` : オンボードLANアダプタ)
Raspberry Pi 2 Model B
[IPアドレス:固定 (192.168.0.50)]
-----------------------------------------
|
(HDMIケーブル)
|
-----------------------------------------
テレビ
-----------------------------------------

 

事前準備

Windows Terminalの準備

以下を参考に、Windows TerminalをMicrosoft storeからインストールします。また、自分がPowerShellに慣れていないため、デフォルトで cmd.exe が動くように切り替えます。
Windows Terminal Tips - Qiita

 

Raspberry Pi ImagerによるOS書き込み

最近は Raspberry Pi Imager を使ってmicroSDにOSを書き込むようになっていました。
「圧倒的に速い」──ラズパイにOSをインストールする新ツール「Raspberry Pi Imager」 (1/2) - ITmedia NEWS

今回はCLIでしかラズパイを使いませんが、ひとまず Raspbian を選んで書き込んでおきます。

 

Raspberry PiにてSSHを許可

以下を参考に、WindowsにOSの入ったmicroSDを接続し、Windows Terminalを使ってmicroSD上に ssh ファイルを置いておきます。

# microSDへ移動
>cd /d E:\

# 空のsshファイルを作成
E:\>cd . > ssh

# 確認
E:\>dir
 ドライブ E のボリューム ラベルは boot です
...
2020/12/31  09:08                 0 ssh

 
ちなみに、macの場合は以下のコマンドでsshファイルを作成します。

% touch /Volumes/boot/ssh

 

Raspberry Piの起動と接続確認

OSの入ったmicroSDRaspberry Piに接続し、電源を入れます。

その後、Windows TerminalからSSHで接続確認をします。

# パスワード認証によるSSH接続
>ssh pi@raspberrypi.local
pi@raspberrypi.local's password: <raspberry>

# Raspberry Piのバージョン確認
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:        10
Codename:       buster

 

Raspberry Piのログインを公開鍵認証に切り替え

パスワード認証から公開鍵認証に切り替えます。

 

Windows TerminalでSSH鍵の生成
# ssh-kegenで生成し、ログインユーザの .ssh フォルダにSSH鍵 pi_rsa を入れる
>ssh-keygen -t rsa -b 4096 -f %USERPROFILE%/.ssh/pi_rsa

# パスフレーズなし
Enter passphrase (empty for no passphrase):

 

SSH用公開鍵をRaspberry Piに登録

Raspberry Piに公開鍵でSSHするために、Windowsで作成した公開鍵を登録します。

しかし、Windowsには ssh-copy-id コマンドがありません。

代替案はいくつかあるようです。
Is there an equivalent to ssh-copy-id for Windows? - Server Fault

上記方法でも良いのですが、手元に何かないかなと思ったところ、Git bashがインストールされていることを思い出しました。

Git bashには ssh-copy-id コマンドがあったため、使ってみます。

$ ssh-copy-id -i ~/.ssh/pi_rsa.pub pi@raspberrypi.local
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
pi@raspberrypi.local's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'pi@raspberrypi.local'"
and check to make sure that only the key(s) you wanted were added.

 
登録できたようなので、Windows Terminalから公開鍵方式でログインしてみます。

# 公開鍵認証によるSSH
>ssh -i %USERPROFILE%/.ssh/pi_rsa pi@raspberrypi.local

# Raspberry Piのバージョン確認
$ lsb_release -a
No LSB modules are available.
...

 

Raspberry Pivimを入れる

デフォルトでは vim-tiny なので、 vim に差し替えます。
RaspberryPi3のセットアップ続き〜VimやNFS設定 - Qiita

# アンインストール
$ sudo apt-get --purge remove vim-common vim-tiny

# vimをインストール
$ sudo apt-get install vim

 

Raspberry Piを固定IP化

以前行ったとおり、 /etc/dhcpcd.conf を修正し、固定IP化します。
Python2 + Scapyで、Raspberry Pi 2 Model B をブリッジにできるか試してみた #router_jisaku - メモ的な思考的な

# /etc/dhcpcd.conf を開く
$ vi /etc/dhcpcd.conf

# 末尾に追加
interface eth0
static ip_address=192.168.0.50/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1

 
追加した内容で有効化します。

$ sudo service dhcpcd reload

 
IPアドレスが変更となるので、Windows TerminalのSSH接続が切れます。そのため、再接続します。

> ssh -i %USERPROFILE%/.ssh/pi_rsa pi@raspberrypi.local

 

/boot/config.txtの編集

デフォルトでは、Raspberry PiHDMI接続したときにCEC信号が送られてしまうため、それを無効化しておきます。

$ vi /boot/config.txt

# 以下を追加
hdmi_ignore_cec_init=1

 

Raspberry PiのデフォルトのPythonをPython3にする

Raspberry PiのデフォルトのPythonのバージョンを見たところ、Python2系でした。

そのため、デフォルトをPython3系へと切り替えます。インストール済はPython3.7でしたが、今回扱う範囲では問題なかったので、Python3系の最新にはしません。
RaspberryPiでPythonのデフォルトをPython2.7からPython3に変更する | そう備忘録

# シンボリックリンクの確認
$ ls -l /usr/bin | grep python
...
lrwxrwxrwx 1 root root          7 Mar  4  2019 python -> python2
...
lrwxrwxrwx 1 root root          9 Mar 26  2019 python3 -> python3.7
...

# 変更
$ cd /usr/bin
pi@raspberrypi:/usr/bin $ sudo unlink python
pi@raspberrypi:/usr/bin $ sudo ln -s python3 python

# バージョン確認
$ python --version
Python 3.7.3

 

cec-clientのインストールと動作確認

まずは、Raspberry PiからHDMI CECを使った操作ができるかを確認します。

cec-clientは cec-utils に含まれるため、インストールします。

$ sudo apt-get update
$ sudo apt-get upgrade -y
$ sudo apt-get install cec-utils -y

 
cec-clientの動作確認をします。

$ sudo cec-client -l
libCEC version: 4.0.4, compiled on Linux-4.15.0-48-generic ... , features: P8_USB, DRM, P8_detect, randr, RPi, Exynos, AOCEC
Found devices: 1

device:              1
com port:            RPI
vendor id:           2708
product id:          1001
firmware version:    1
type:                Raspberry Pi

 
cec-clientを使った操作ですが、Raspberry Pitvservice をoffにしておかないと動作しません。

$ echo "scan" | cec-client -d 1 -s

# エラーが出て動かない
log level set to 1
opening a connection to the CEC adapter...
ERROR:   [             421]     RegisterLogicalAddress - CEC is being used by another application. Run "tvservice --off" and try again.
ERROR:   [             421]     Open - vc_cec could not be initialised
ERROR:   [             421]     could not open a connection (try 1)

 
そこで、 tvserviceをoffにします。
Raspberry Pi Documentation

$ tvservice -o
Powering off HDMI

 
再度実行すると、scanや電源ON/OFFができました。

# Scan
$ echo "scan" | cec-client -d 1 -s
log level set to 1
opening a connection to the CEC adapter...
requesting CEC bus information ...
CEC bus information
===================
device #0: TV
address:       0.0.0.0
active source: no
vendor:        Unknown
osd string:    TV
CEC version:   1.4
power status:  standby
language:      ???


device #1: Recorder 1
address:       1.0.0.0
active source: no
vendor:        Pulse Eight
osd string:    CECTester
CEC version:   1.4
power status:  on
language:      eng


currently active source: unknown (-1)

# 電源ON
$ echo 'on 0' | cec-client -s 
...
DEBUG:   [            1814]     >> TV (0) -> Recorder 1 (1): report power status (90)
DEBUG:   [            1814]     expected response received (90: report power status)
DEBUG:   [            1814]     << requesting vendor ID of 'TV' (0)
DEBUG:   [            1814]     'give device vendor id' is marked as unsupported feature for device 'TV'
NOTICE:  [            1814]     << powering on 'TV' (0)
TRAFFIC: [            1815]     << 10:04
DEBUG:   [            1906]     TV (0): power status changed from 'standby' to 'in transition from standby to on'

# 電源OFF (スタンバイ)
$ echo 'standby 0' | cec-client -s
...
TRAFFIC: [            2590]     >> 01:9f
DEBUG:   [            2591]     >> TV (0) -> Recorder 1 (1): get cec version (9F)
TRAFFIC: [            3351]     >> 0f:36
DEBUG:   [            3351]     TV (0): power status changed from 'on' to 'standby'
DEBUG:   [            3351]     >> TV (0) -> Broadcast (F): standby (36)

 

python-cecを使った操作

ここからが本題です。

今回はpython-cecを使い、Raspberry Piからテレビを操作します。
trainman419/python-cec

 

Windows上のPyCharmのPythonインタプリタRaspberry PiPythonにする

Raspberry Pi上で実装しても良いですが、せっかくので、ローカルのWindows上で実装したものをRaspberry Pi上で実行することにします。

なお、この方法はPyCharm Professionalが必要です。

 

Raspberry Pi上でvenv上にpython-cecを入れる
# ディレクトリを作り移動
pi@raspberrypi:~ $ mkdir projects
pi@raspberrypi:~ $ cd projects/
pi@raspberrypi:~/projects $ mkdir python_cec_sample
pi@raspberrypi:~/projects $ cd python_cec_sample

# venv環境を作る
$ python -m venv env
$ source env/bin/activate

# python-cecを入れる
$ pip install cec --no-cache-dir
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting cec
  Downloading https://www.piwheels.org/simple/cec/cec-0.2.7-cp37-cp37m-linux_armv7l.whl (146kB)
    100% |████████████████████████████████| 153kB 268kB/s
Installing collected packages: cec
Successfully installed cec-0.2.7

 

Windows上でPyCharmからプロジェクトを作成する

File > New Project... から新しいPythonプロジェクトを作成します。

設定は以下のようにします。

  • 左ペインで Pure Python を選択
  • 右側の Location に、ローカルに保存する場所(例:D:\projects\python_cec_sample) を指定
  • Python Interpreter欄にある、 Previously configured Interperter の右ボタンより、 Add Python Interpreter へ遷移
  • SSH Interpreter を選択
  • Existing server configuration の右ボタンより SSH configurations へ遷移
  • + を押して追加
  • 設定内容
    • Host: 上記で設定したRaspberry Piの固定IP (192.168.0.50)
    • Port: 22
    • Username: pi
    • Authentication type: Key pair
    • private key file: 上記で作ったprivateキー pi_rsa の場所
    • Passphrase: 空欄
  • Test connectionをクリック、接続できればOKとする
  • Connected to pi@192.168.0.50:22 のInterpriter指定は、Raspberry Piのvenv環境のPythonを指定 (/home/pi/projects/python_cec_sample/env/bin/python)
  • Execute code using this interpreter with root privileges via sudo にチェックを入れる
  • Remote project locationには、 /home/pi/projects/python_cec_sample を指定

 
上記により、PyCharm上で import ce と入力したときの補完が効くようになります。

もし補完が効かない場合は、以下を参考にリモートの再読み込みを行います。
【PyCharm】リモートインタプリタでライブラリ追加した際に正しく認識させる方法 | ゆとって生きたい。

  • Project Interpreterの歯車マークで Show All... を選択
  • ツリーマーク (Show paths for the selected interpreter) をクリック
  • リフレッシュマーク (Reload List of Paths) をクリック

 

Pythonスクリプトの作成

python-cecのREADMEを読むと、できることが一通り書かれています。

そのため、以下のようなPythonスクリプトを用意します。

このPythonスクリプトを実行すると、テレビの電源ON/OFFや音量調整ができたり、情報を出力できました。

import cec

def main():
    cec.init()

    tv = cec.Device(cec.CECDEVICE_TV)

    # 電源が入っているか
    print(tv.is_on())
    # => True / False


    # if tv.is_on():
    #     # 電源がONの場合、次はスタンバイにする
    #     tv.standby()
    # else:
    #     # 電源が入っていない場合、電源を入れる
    #     tv.power_on()

    # ベンダ
    print(tv.vendor)
    # => 000000

    # 言語
    print(tv.language)
    # => ??? (電源ONの場合は、jpn)

    print(tv.osd_string)
    # => TV

    print(tv.cec_version)
    # => 1.4

    # 音量周りは、一度にどちらかだけ
    # 音量を一段階上げる
    # cec.volume_up()
    # 音量を一段階下げる
    cec.volume_down()


if __name__ == '__main__':
    main()

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/python_cec-sample