2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Raspberry piとTVをHDMIで繋げてTVを制御する。

Posted at

前書き

Raspberry pi4bもjetson nanoも定価で全然売っておらず、買えてません。欲しい・・・(前回に続いて2回目。)

前回の記事LINE BOT経由でパパの居場所(androidスマホ)を地図で教える(成功編)に続いて、TVとHDMIでつながっているRaspberry piを活用してTVを制御してみます。
子供が学校から帰ってきて、TVばっかり見て困っているので、考えてみました。

TVの電源ON/OFFのログが見れたり、TVを起動禁止(10秒周期でTV電源のON/OFFを検知し、ONだったら消す)にしたりできます。

line1.jpg line2.jpg

ご指摘・ご質問あれば、コメントいただければと思います。

構成&動作

以下に構成図と簡単な動作を示します。
tvban.png

TVとRaspberry piをHDMIで接続します。
Raspberry piは、CEC対応なので、そのまま使用します。

確認者がLINEでTVの状態の確認、TVの過去の電源ON/OFFの履歴および禁止のON/OFFが設定できます。
docker内でpythonを使用して、libCECライブラリ経由でTV情報を取得しています。
TVの過去の電源ON/OFFの履歴および禁止のON/OFFの設定については、firebaseのFirestore Databaseに保存しています。

複数ホストでdockerを動作させているため、オーケストレーションツールの導入を一度は検討しましたが、断念しました。
Docker SWARM: docker-compose.ymlのdevices:の記載が機能しなくなるため、見送りました。
k8s: メモリを使いすぎるので、導入できず。(Raspberry Pi4欲しい・・・。)

構築

環境の構築については、
記事LINE BOT経由でRaspberry Piに接続したUSBカメラの画像を取得してみるを参照ください。

また、前回の記事LINE BOT経由でパパの居場所(androidスマホ)を地図で教える(成功編)でFirebaseのCloud Firestore(Cloud Messagingに使用するトークンの保存に使用)を使用しましたので、今回もTVの禁止設定とログに使用したいと思います。

CEC制御部分のpythonコード

今回の肝であるCECの制御部分の説明です。
python3-cecパッケージに含まれる/usr/share/doc/python3-cec/examples/pyCecClient.pyを参考にしました。

Raspberry pi2で何故か、TVのステータスが何故かunknownになってしまう、/dev/cec0を見に行くなど発生しました。
色々調べてみると、/boot/configのdtoverlay=vc4-kms-v3dで指定しているドライバが原因と分かりました。
/boot/config.txtで、"dtoverlay=vc4-kms-v3d"となっていないかチェックしてください。
もし、なっていれば、"dtoverlay=vc4-fkms-v3d"に変更して試してみてください。

cec_control.py(1)
# for cec
import cec

# for flask
from flask import Flask, jsonify

# for environment
import os

app = Flask(__name__)

CECの制御部分はWebAPI形式で提供するので、Flaskをインポートしています。
あと、もちろんCECもインポートします。

cec_control.py(2)
    # create new libcec_configuration
    cecconfig = cec.libcec_configuration()

    # set configulation
    set_config(cecconfig, cec_control_name)

    # set keypress callback
    set_keypresscallback(cecconfig, key_press_callback)

    # Initialise
    adapter = cec.ICECAdapter.Create(cecconfig)
    adapter.InitVideoStandalone()

    # Open adapter
    adapters = adapter.DetectAdapters()

    if (len(adapters) < 1):
        print("can not find adapter")
        exit(0)

    # get my adapter info
    my_adapter = adapters[0]
    print("my_adapter is :")
    print("  port:     " + my_adapter.strComName)
    print("  vendor:   " + hex(my_adapter.iVendorId))
    print("  product:  " + hex(my_adapter.iProductId))
    adapter_name = my_adapter.strComName

    if adapter.Open(adapter_name):
        print("connection opened.")
    else:
        print("unable to open the device on port " + adapter_name)
        exit(0)

CECの初期化部分です。
ほぼ定型です。

cec_control.py(3)
# Get TV's power status
@app.route('/GetTVPowerStatus', methods=['GET'])
def getTVPowerStatus():
    power = adapter.GetDevicePowerStatus(0)
    pow_str = adapter.PowerStatusToString(power)
    json = {
        'status': pow_str,
        'name': cec_control_name
    }
    return jsonify(json)

初期化で取得したadapterでGetDevicePowerStatus(0)を読んでます。
0は論理アドレスでTVが割り当てられていますので、テレビの電源状態が取得できます。
取得できるのは数字のため、人間が読めるようにadapter.PowerStatusToString(power)で変換後、
名前とセットでjsonにして返しています。
電源が入っている場合は"on"、電源が切れている場合はstandbyが取れます。

cec_control.py(4)
# Set TV's power
@app.route('/SetTVPower/<string:controlType>', methods=['PUT'])
def setTVPower(controlType):
    result = False
    if controlType == 'on':
        result = adapter.PowerOnDevices(0)
    elif controlType == 'off':
        result = adapter.StandbyDevices(0)
    json = {
        'result': result,
        'name': cec_control_name
    }
    return jsonify(json)

URLでon,offを指摘すると、電源のON,OFFが出来ます。
adapter.PowerOnDevices(0)、adapter.StandbyDevices(0)ですので、ターゲットはTVとなります。

cec_control.py(5)
@app.route('/GetActiveSource', methods=['GET'])
def getActiveSource():
    activeSource = adapter.GetActiveSource()
    print('activeSource is ', activeSource)
    as_str = adapter.LogicalAddressToString(activeSource)
    json = {
        'activesource': as_str,
        'name': cec_control_name
    }
    return jsonify(json)

adapter.GetActiveSource()で現在、テレビに表示しているソースを取得できます。
ちなみに本APIはどこからも呼ばれておりません。

cec_control.py(6)
# Be Active Source
@app.route('/BecomeActiveSource/<string:target>', methods=['PUT'])
def becomeActiveSource(target):
    result = False
    if target == 'TV':
        result = adapter.SetActiveSource(0)
    else:
        # If target is other than TV, my device will be active source.
        result = adapter.SetActiveSource()
    json = {
        'result': result,
        'name': cec_control_name
    }
    return jsonify(json)

adapter.SetActiveSource()で引数に指定した論理アドレスの機器入力を有効にします。
ですので、TVであれば0を指定します。
ちなみに指定しなければ、自身(Raspberry Piの入力)が有効になります。
ちなみに本APIはどこからも呼ばれておりません。

これらのWebAPIを外部から呼び出すことにより、TV操作を制御します。

CEC制御部分のdockerイメージ作り

dockerイメージのbaseは、Raspbianでないといけないです。raspberry用のlibcec用ドライバとかを入れるので。
ですが、探したのですが、公式のイメージは少し古いですので、現時点で最新のBullseyeを使用したイメージを作成します。

$ docker image import https://downloads.raspberrypi.org/raspios_lite_armhf/root.tar.xz raspiarm32-lite
Downloading from https://downloads.raspberrypi.org/raspios_lite_armhf/root.tar.xz
Importing [==================================================>]  311.3MB/311.3MB
sha256:ee3e4b69b70506aab7751d8922bc8cd6e7741dfd3132c76a1f41d8f548194f2d
$

raspberry piの公式から、raspberry pi os のlite(GUIが無いやつ)のroot.tar.gzをインポートして、イメージを作成します。

FROM raspiarm32-lite:latest

ENV TOP_DIR /cec_control

RUN mkdir ${TOP_DIR}

COPY cec_control.py ${TOP_DIR}

WORKDIR ${TOP_DIR}
RUN apt-get update && apt-get install -y \
    libcec6 \
    python3-cec \
    python3-flask

CMD ["/usr/bin/python3","cec_control.py"]

libcec6はlibcec関連のパッケージ、python3-cecはpythonのlibcecバインディングとかが入っているpythonからcecを使うのに必要なパッケージです。

docker-compose.yml
  cec_control_LIVING:
    image: docker_cec_control:latest
    restart: always
    networks:
      - docker_net
    environment:
      CEC_CONTROL_HOST: "cec_control_LIVING"
      CEC_CONTROL_PORT: "3001"
      CEC_CONTROL_NAME: "LIVING"
    devices:
      - "/dev/vchiq:/dev/vchiq"

docker_cec_controlは、上のDockerfileから作成されたイメージです。

"/dev/vchiq:/dev/vchiq"

Raspberry PiのlibCECは、/dev/vchiqデバイスが必要のようなので、docker上からも触れるように設定します。

あとは、外部から制御する部分だけですので、詳細は割愛します。
ソースコードをご確認ください。

CEC制御のソースコード:cec_control
heroku上のソースコード:tvca-line-bot
docker上のjavascriptのソースコードTVCArouter

総括

cec-clientを使用してTVを制御する記事はよく見かけますが、pythonからAPIを呼び出す記事が少なかったので、書いてみました。

この記事が少しでも、他の方の役に立てば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?