Raspberry Piでおうちサイネージを作ってみよう!

おうちサイネージとは

おうちサイネージというのは、家のリビングなどに設置するスマートディスプレイのことです。

日常生活で役に立つ情報をいい感じに表示することで、生活がより便利になります。

f:id:inajob:20200229180713p:plain

おうちサイネージの要件

ここで扱うおうちサイネージに求める要件はこんな感じです。

  • 常時起動せず必要な時に起動する(電気代削減)
  • ディスプレイをほかの機器と共用する
  • 直近の予定を表示する
  • 時刻を表示する

基本的な構成

基本的な構成はRaspberry PiとPCモニタです。

(いま新たに買うならRaspberry PI 4のほうがおすすめですが、我が家にはRaspberry Pi 3が転がっていたのでこれを使っています。)

我が家ではHDMI出力のあるテレビチューナをPCモニタにつなげてリビングに置いています。そこにRaspberry Piを追加するということです。

 

f:id:inajob:20200229174006p:plain

必要な時に起動する

「常時起動せず必要な時に起動する」「ディスプレイをほかの機器と共用する」を実現するためには、Raspberry Piをスリープモードに出来れば良さそうですが、実際のところRaspberry Piはスリープすることができません。

ここでは妥協案として「HDMI出力を停止する」ことでこの要件を達成します。

一般的なPCモニタはHDMI出力を停止するとスタンバイモードに入ります、また複数の入力を受け付けるモニタや切り替え機の「自動切換えモード」も、出力がある機器に自動的に切り替えてくれるため、おうちサイネージの起動のためにリモコンで入力切替をする必要がありません。

 

XOrgの機能として、「一定時間入力がないと勝手に画面がブランクになる」というものがあり、まずはこれを当てにしていたのですが、どうもこの「ブランクになる」という動作ではHDMI信号は出続けており、ディスプレイも点いたままですし、ほかの機器を起動しても、Raspberry Piの「黒い画面」を表示し続けてしまいました。(入力切替を手動で実行すると切り替わりますが・・)

 

いろいろ調べているとtvserviceというコマンドでHDMI接続したモニタの電源を操作できることがわかりました。

具体的には下記のコマンドでディスプレイの電源が切れます。

$ tvservice -o

そして下記のコマンドでディスプレイの電源が点きます(環境によってパラメータが違うかもしれません)。

$ tvservice -p
$ fbset -depth 8
$ fbset -depth 32
$ xrefresh -d :0.0

起動のトリガを作る

 プログラムからRaspberryPiと接続されたPCディスプレイの電源をON/OFFできることがわかりましたが、どうやって「必要な時に起動する」を実現するかが問題です。

ここでは「Escキー」を押すことでおうちサイネージを起動するようにしました。起動後は3分経過すると再びPCディスプレイの電源を落とすことにします。

 

キーボードの入力を受けるためには、一般的に考えると、そのウィンドウにフォーカスが当たっている必要ありそうですが、おうちサイネージとしてはいくつかのプログラムを画面分割して動作させたいと考えていたので、ウィンドウにフォーカスが当たっていなくてもキーの入力をキャプチャしたいと考えました。

 

ちょっと調べてみると、まさにこれを実現するためのPythonのライブラリを発見しました。

github.com

 これを使うことで、CUIのプログラムから、どのウィンドウにフォーカスがあってもキー入力をキャプチャできるようになりました。

前述のPCディスプレイの電源周りのコードと合わせてプログラムにするとこんな感じです。

import keyboard
import subprocess
import time

while True:
    # standby
    subprocess.run(["tvservice", "-o"])
    
    # wait untill escape pressing
    keyboard.wait('esc')
    
    # resume
    subprocess.run(["tvservice", "-p"])
    subprocess.run(["fbset", "-depth", "8"])
    subprocess.run(["fbset", "-depth", "32"])
    subprocess.run(["xrefresh", "-d", ":0.0"])
    
    # start timer
    time.sleep(60 * 3)

画面レイアウト

おうちサイネージでは複数のプログラムを起動して情報を表示したいと考えました。そのレイアウトを簡単に固定するためにdwmというタイル型ウィンドウマネージャを採用しました。

dwmでは画面左をマスターと使用し、残る右部分を縦分割してスタックとして利用するレイアウトを実現できます。

f:id:inajob:20200229174910p:plain

実際のところはメインパネルで動くプログラム以外はデバッグ用のコンソールとなっていますが、ほかのプログラムを動かす際はdwmのパネルレイアウトが役に立つと考えています。

f:id:inajob:20200229175041p:plain

カレンダーの取得

我が家は家族カレンダーとしてTimeTreeを使用しています。最近TimTreeはAPIを公開したらしい、というので今回はTimeTreeのAPIを使ってカレンダーを取得しました。

developers.timetreeapp.com

下記のようにコマンドラインでも簡単にカレンダーの内容を取得できました。(実際はPythonのプログラムで取得し、後述のtkinterで画面に表示しています。)

$ curl 'https://timetreeapis.com/calendars/<カレンダーのID>/upcoming_events?timezone=Asia/Tokyo&days=3&include=creator,label,attendees' \
  -H "Accept: application/vnd.timetree.v1+json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

メイン画面

おうちサイネージのメイン画面は初めはChromiumを起動して、Webアプリとして実装していたのですが、Raspberry Pi 3には重荷のようで、動作がもっさりしてしまいました。

そこでPythontkinterというライブラリを使い軽量な画面を作ることにしました。

tkinterはあまり使ったことがなかったのですが、この程度の画面であれば非常に簡単に作ることができました。printfでコンソールに出すだけでは、味気ない情報をGUIとして表示する際には非常に便利なツールです。

↓デフォルトだとちょっと古めかしいスタイルで描画されます。これはこれで味がある・・

f:id:inajob:20200229181126p:plain

リモートデバッグ環境

おうちサイネージはリビングのRaspberryPiで動いていますが、その画面の開発などはできれば自室のメインPCから行いたいところです。簡単なロジックはメインPCで作ることができますが、表示確認などは、実機の画面を見ながら行うのが楽です。

メインPCとRaspberry Piを行ったり来たりするのはストレスなので、うまくリモートデバッグする方式としてx11vncを使うことにしました。

VNCサーバによっては、表示されている画面とは別のもう一つの仮想スクリーンを提供するものがありますが、今回はそうではなくメインディスプレイと同じものを配信するミラーリングの機能が必要でした。

 ↓Windowsマシンからアクセスしている様子。

f:id:inajob:20200229175506p:plain

リモートデバッグ環境を整えることで開発サイクルが加速させることができました。

 起動のための物理ボタン

おうちサイネージの起動は前述のように「Esc」キーを押すことにしました。

しかし、このためだけにリビングのディスプレイの前にキーボードを置いておくのもナンセンスです。

そこで、Arduinoを使ってEscキーだけのキーボードを自作しました。Arduino Pro Mircoを使うとこのようなカスタムキーボードを作ることは簡単です。

プログラムはこれだけです。

#include "Keyboard.h"

#define BTN1 6

void setup() {
  Keyboard.begin();
  pinMode(BTN1, INPUT_PULLUP);
}

void loop() {
  if(digitalRead(BTN1) == LOW){
    Keyboard.press(KEY_ESC);
    delay(100);
    Keyboard.releaseAll();
    while(digitalRead(BTN1) == LOW);
  }
  delay(100);
}

↓回路はこんな感じ。6番ピンとGNDにスイッチの両足をつなげているだけです。基板とボタンは直結しても良いのですが、なんとなくソケットを使って分離できるようにしています。

f:id:inajob:20200229175717p:plain

さらにこのArduino Pro Microとスイッチをマウントするためのケースを3Dプリンタで作成しました。

f:id:inajob:20200229175913p:plain

f:id:inajob:20200229175823p:plain

完成!

ここまでの成果をガガガっと組み合わせるとこんな感じになります。

まだまだ 改良したい点はたくさんありますが、ひとまず形になったので、ここまでの苦労と知見を記事にしてみました。

 

さぁ、これを見ている皆さんも「おうちサイネージ」作ってみませんか?