raspberry pi 3B+でテレビ電話システムを構築する

はじめに

この投稿ではサーバーの用意が必要なので、sapislabを使ったやり方を用意しました。
サーバー立てるの厳しい方はこちらをご覧ください。

システム概要

raspberry pi 3B+ によるテレビ電話システム
※記載内容に責任は持てません、全て自己責任でお願いします

概念図

f:id:miya15:20190222233504p:plain

背景

  • 妻が夕食の準備中に子供の相手が大変である
  • おばあちゃんが一人なので心配である

改善案

  • 子供とおばあちゃんが気軽にテレビ電話が出来るようにする

要件

  • テレビ電話が出来ること
  • 手軽に使えること(煩雑だと結局使われない)
  • 費用は抑えること(常套句)

想定シナリオ

  • 妻が子供たちを連れて帰宅
  • 我が家からリモコンでスイッチオン
    • 我が家とおばあちゃん家のテレビが自動でONになりテレビ電話を開始する
  • 我が家からリモコンでスイッチオフ
    • 我が家とおばあちゃん家のテレビが自動でOFFになりテレビ電話を切断する

前提条件

  • おばあちゃんに家のテレビが勝手にONになってリビングが映し出されるプライバシー的な問題について予め了承を得ておくこと(※同居人が居る場合はその方にも)
  • VPNは張らない(ルーターの機能的に無理だからw)

用語

  • 我が家はcallerに置き換える(呼び出し側)
  • おばあちゃん家はreceiverに置き換える(受け手側)
  • 途中に出て来るvtcはvideo telephone controlerを略したもの

必要なもの

  • raspberry pi 3B+ を2セット
    • ヒートシンクは付けておいた方が良いと思う
    • 電源やSDカード等の別途必要なものは明示しない
    • OSはraspbianを想定
  • webカメラを2個
    • 1000円程度のもので十分
  • USBマウス・USBキーボード
    • セットアップ用途
  • AB Shutter3 を1個
    • シェル起動のトリガーになるなら何でも構わない
  • caller,receiver両方のネットワーク環境(従量制ではないこと)
  • APIサーバーを立てる場所(及びデプロイできる技量)
    • グローバルIPが当ててあるか、DNSで特定できるようになっていること
    • ここでは一般的なVPSを想定
    • raspberry piで何かしようとしている人ならきっとできる(偏見)
  • テレビ電話サービスのアカウント登録用メールアドレスおよびアカウント
    • アカウント登録手順などは省略

仕組み

  • APIサーバーを仲介役として、callerから指示を出し、receiverがポーリングで反応する
    • こうする事でIPが変わる環境でも間接的な連携が可能となる
    • ネットワーク的にも分断されたままなので安心(?)
  • テレビ電話はskypeも検討したが直接はインストールできない為、ブラウザベースのものを利用
    • 特定のURLにアクセスしたら繋がるタイプ(apeer.inやskyway)を使う
    • たまたま同じルームを作ってしまって丸見えになる覚悟も必要(skywayでローカルに立てる方式なら問題無さそう)
      • 絶対にNGであれば有料会員になってLockすれば大丈夫だと思う(試してない)

状態遷移図

f:id:miya15:20190228163315p:plain

開始処理のシーケンス図

f:id:miya15:20190228163332p:plain

終了処理のシーケンス図

f:id:miya15:20190228163340p:plain

APIサーバーの機能(超単純)

  • 現在の状態を取得できる
  • 状態を変更できる
  • 接続すべきURLを取得できる
  • セキュリティ関連
    • 必要に応じてapiキーによる制御

やり方

raspberry pi 3(caller, receiverともに)

raspberry pi 3(receiverのみ)

  • 「vtc-exec-receiver.sh」をcronで定期実行させる(間隔はお任せ)
  • 注意:シェルスクリプトAPIサーバーのURLとAPIキーを記載する場所があるので適宜入れておくこと

raspberry pi 3(callerのみ)

  • AB Shutter3 で任意のシェルが叩ける準備(下記の記事を参考に準備する)

miya15.hatenablog.com

  • ON用のボタンが押されたら「vtc-exec-caller-on.sh」を叩くようにする
  • OFF用のボタンが押されたら「vtc-exec-caller-off.sh」を叩くようにする
  • 注意:どちらのシェルスクリプトにもAPIサーバーのURLとAPIキーを記載する場所があるので適宜入れておくこと

APIサーバー

  • docker, docker-compose が入っていなければインストール
    • 直接配置したい人は無くても問題無い
    • ssl付ける人でlet's encrypt使う場合はapache等でリバースプロキシすると良い
      • ssl付けないとapiキー付けてもキャプチャされたら無防備な点に注意
      • sslの詳細は省きます
  • プログラム配置
  • redisを直接操作してapiキーやurlを初期設定する
    • 詳細は後述
    • apiキーはcallerとreceiverを分けて発行した方が後々の拡張で使えるかもしれません(現状では何ら分ける意味はありません)

redisへの設定値反映方法

redis-cliを叩く手順(docker-composeの利用を想定)

$ docker exec -it vtc-api-server_db_1 /bin/sh
/data # redis-cli
127.0.0.1:6379> コマンド(後述参照)

コマンド例

▽APIキーを追加する
127.0.0.1:6379> rpush apikeys xxx
▽urlを設定する(※使える記号は「:/.?=&」)
127.0.0.1:6379> set url xxx
▽状態をrequest to openに設定する
127.0.0.1:6379> set state "request to open"

各種プログラム

注意事項

  • 電化製品なので漏電や火災に気を付けましょう
  • 各種OSやミドルウェアのバージョンを新しく保つよう心がけましょう
    • raspberry pi もパソコンですからBOT化しないよう気を付ける
  • テレビによってはHDMIの制御が効かないものがあります、ご注意ください
    • まずはraspberry piを1台だけ買って双方のテレビに繋いでコマンド投げて試す方が良い
  • 接続先URLをAPIサーバーから取得するようになっているので気になる方は直接プログラムに埋め込んだ方がより安全です
    • URLが変更できるようになっているのはルームIDを変更したり別のサービスに切り替えるのが手軽に出来るようにする為です

今後の発展

  • 状態遷移はjson形式で渡しているので遷移を増やすと面白いかもしれない
  • 定期的にAIで在席・不在を判断できるとその後のアクションにも繋げられるか

運用中に発生した問題点について

  • receiver側でHDMIの制御がうまくいかない事があった
    • 具体的にはch2に繋いでいるのに"as"で入力1になってしまう。ログを取ったが物理アドレスを誤認識している様子。仕方ないので直接chを指定する形で回避。コメントアウトしているが「cmd = "echo 'tx 1f:82:20:00' | cec-client -s"」の部分。1f:82:20でch2、1f:82:10でch1となるがテレビによって違う可能性あり。
  • 実際に繋がると子供(5歳男児)は映っている自分に興奮して踊りだし、おばあちゃんとの会話にならない(苦笑いしてるというありさま)
    • 完全に想定外

メモ

  • APIサーバーはAWSapi gatewayとlambdaとDynamoDBの組み合わせでも問題無いと思うが今回はパス(悪意のあるユーザーに大量に叩かれたら嫌なだけ)
  • Herokuの無料プランでも使えるかもしれない
    • ちょっと試したけどredis使うならクレジットカード登録が必要ですね
  • 今回はraspberry piを使う事が前提にあったが、intel系のスティックPCとかでskypeを使う方式としても使えるかもしれない(skypeには自動応答機能があるので、コマンドラインからコール出来れば完成したようなもの)
  • プログラムが最適化されていない点はご容赦ください
  • バグなどありましたらコメント頂けるとありがたいです
  • 取り敢えず1ヵ月ほど運用してみましたがうまく動いています(思ったほど活用されてないのが残念ですが・・・)

redmineでPlantUMLを表示する際にサーバー側で画像を生成させる方法

概要

PlantUMLは便利ですが画像生成をするアプローチに苦慮します。
記述している内容を秘匿する必要がある場合は尚更です。
今回はredmineプラグインplantuml-redmine-macroに手を加えて社内サーバーで画像を生成させ、それをHTTPレスポンスに含めるやり方を説明したいと思います。

やり方

まずはプラグインのご紹介。

bitbucket.org

このままだと公式サーバーの方に情報が流れてしまうので社内サーバーを立てます。
今回もDockerで手軽に立てましょう。docker-hubはこちらになります。
ポート番号などは任意で。

$ docker run -d -p 8000:8080 plantuml/plantuml-server

次にredmineプラグインの設定(管理→プラグイン→PlantUML Macro plugin→設定)から「PlantUML Server URL」の項目に立てたサーバーのURLを設定します。
「適用」ボタンをクリックするのを忘れずに。

設定例
http://192.168.0.200:8000/

さて、ここまでの内容でredmineに下記のような記述でPlantUMLの画像が埋め込まれる仕組みが出来ました。

{{plantuml
    Bob -> Alice : hello
}}

しかし、このままだとHTTPレスポンスにはimgタグが含まれるだけで、クライアント側では改めてPlantUMLサーバー(設定値のURL)に問い合わせる事で画像を手に入れます。
そこでinit.rbの内容を下記のように書き換える事でWEBサーバー側で画像を生成してHTTPレスポンスに含めて返す事ができます。
※設定したURLでredmineサーバーからPlantUMLサーバーへアクセスできる必要があります
※このPlantUMLサーバーへのアクセス経路がプライベートネットワークであれば侵入されない限り漏えいしません

require 'redmine'
require 'uri'
require 'plantuml'
require 'open-uri'
require 'base64'

Redmine::Plugin.register :plantuml_macro do
  name 'PlantUML Macro plugin'
  author 'Denis Nelubin'
  description 'This is a plugin for Redmine to convert PlantUML text into link to the image on PlantUML server'
  version '0.0.1'
  url 'https://bitbucket.org/gelin/plantuml-redmine-macro'
  author_url 'http://google.com/+DenisNelubin'

  settings :default => { 'plantuml_url' => 'http://plantuml.com/plantuml/' },
           :partial => 'settings/plantuml_macro_settings'

  Redmine::WikiFormatting::Macros.register do

    desc "Converts PlantUML text into link to the image on PlantUML server. Example:\n\n" +
         "  {{plantuml\n" +
           "  Bob -> Alice : hello\n" +
           "}}"
    macro :plantuml do |obj, args, text|
      url = URI.join(Setting.plugin_plantuml_macro['plantuml_url'] + '/', 'png/')
      encoded = PlantUML.encode(text)
      base64image = ''
      open(URI.join(url, encoded).to_s) do |file|
         base64image = Base64.strict_encode64(file.read)
      end
      image_tag("data:image/png;base64," + base64image)
    end

  end

end

raspberry pi で無線LANの設定(ステルスモード対応)

概要

raspberry pi無線LANの設定をする際、ステルスモードになっている為にGUIから設定できない場合の対応方法をメモします

やり方

$ sudo vi /etc/wpa_supplicant/wpa_supplicant.conf

で下記を追記

network={
    ssid="ここにSSID"
    psk=ここにプレシェアードキー
    scan_ssid=1
}

※既存のnetwork項目があったら、新たに追記でOK

radikool6でm4aからmp3へ変換するメモ

概要

radikoをタイマー録音する手法を探していてradikoolにたどり着きました。
radikool6はdockerで動くようなので早速自宅に設置してあるesxiに設置してみると、テスト版とはいえ任意の過去番組(タイムフリーによるもの)をダウンロードする事ができました。

公式サイトはこちら
blog.ez-design.net

今回はm4aからmp3に変換する手順をメモしておきます。

事前作業

コンテナに入って必要なパッケージをインストールします。

ここからホスト
$ docker exec -it radikool6_radikool_1 /bin/bash
ここからコンテナ
$ apt-get update
$ apt-get install ffmpeg libavcodec-extra57

変換作業

事前作業が済んでいれば下記のように変換できます。

ここからホスト
$ docker exec -it radikool6_radikool_1 /bin/bash
ここからコンテナ
$ cd /Radikool6/wwwroot/records/
$ ffmpeg -i xxx.m4a -ab 128k yyy.mp3

これでxxx.m4aをyyy.mp3に変換できます。オプションのabはビットレートを指定します。

あとがき

まだ超alpha版とのことですが、完成を楽しみに待っております。
できれば毎週予約機能が欲しいですw

問題1

問題

次の関数を作ってください

  • 引数で配列データ(中は文字列)を受け取る
  • 予め決められた文字列(例えば apple,orange,banana)と順序問わず等しいか判定する
  • 戻り値は0か1とする
    • 0 : 一致しない
    • 1 : 一致する

補足

  • 言語は問わない
  • 「予め決められた文字列」は関数内に定義して構わない
  • 引数で渡される配列の要素数は未定(0個かもしれない)
  • 大文字小文字は区別してもしなくても構わない

cec-clientで電源ONが効かなかった時の確認事項

概要

raspberry pi 経由でcec-clientを使ってHDMIから電源ON/OFFしたいのですが、テレビによってはうまくいかないことがありました。
今回はテレビ側の設定で解決できたので一例として投稿します。

機種

今回うまくいかなかった機種はSHARPのLC-40AE7です。

エラーの確認方法

cec-clientがインストール済みである前提で話を進めます。
テレビの電源をOFF(正確にはスタンバイ)にして下記のコマンドを実行しましょう。

$ cec-client

これで対話モードで実行されるので「waiting for input」が表示されたところで「on 0」と入力。
すると下記のメッセージが表示されました。

AQUOS LINK 'auto power on' is disabled, which prevents the TV from being powered on. To correct this, press the menu button on your remote, go to 'link operation' -> 'AQUOS LINK setup' -> 'Auto power on' and set it to 'On'

適当な和訳ですが「アクオスリンクの自動電源ONが無効だからパワーオンは防止されました。正しく行いたいなら[リンクオペレーション]->[アクオスリンクセットアップ]->[自動電源ON]の設定値をONにしてください。」と読めます。
要するにテレビ側の設定で無効化しているだけということのようですね。

対処方法

実際にテレビの設定を変えるだけで「echo 'on 0' | cec-client -s」が使えるようになりました。
なのでメーカーや機種によって「~リンク」は異なるとは思いますが、セキュリティ対策でデフォルトが自動電源ONを無効化している可能性を疑ってみるのが良いかもしれません。

おまけ

今回はスタンバイ操作はできており、ONが出来ないという中途半端な状態だったので希望をもって調査できましたが、スタンバイ操作も何も反応しない場合はそもそもCEC対応していないとか、ケーブルが古すぎ(安すぎ)で対応していないとか、そういった可能性も考慮する必要があるかもしれません。

docker-composeでflaskを手軽に試す為の小さなテンプレート

概要

小規模なAPIを作りたいと思い、調べてみるとpythonのflaskがシンプルで良さそうでした。
もちろんサーバーも小規模で良いのですがやはりdockerを使って既存サーバーに同居させる方針が良いと思い書きました。
単体で済むのでdocker-composeにする必要は無いレベルですが、起動時のオプションを気にする必要が無かったり拡張性も考慮してcompose化しております。

参考

flaskのサンプルコードを参考にさせて頂きました

qiita.com

docker-compose一式

必要なプログラムもdocker-compose.ymlも下記githubに置いてあります。
使い方もREADME.mdを合わせてごらんください。

github.com

個別ファイル

最新はgithubですが、個別に見たい場合は下記をどうぞ。

Dockerfile

FROM python:3.7-alpine3.7

ARG project_dir=/app/

WORKDIR $project_dir

RUN pip install flask

CMD ["python", "main.py"]

main.py

from flask import Flask, jsonify, request
import json
app = Flask(__name__)

@app.route("/", methods=['GET'])
def hello():
    return "route get. Hello!"

@app.route('/reply', methods=['POST'])
def reply():
    data = json.loads(request.data)
    answer = "route post. keyword is %s!\n" % data["keyword"]
    result = {
      "Content-Type": "application/json",
      "Answer":{"Text": answer}
    }
    # return answer
    return jsonify(result)

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=5001,debug=True)

docker-compose.yml

version: '3.3'

services:
  webapi:
    build: ./flask
    ports:
      - "5001:5001"
    volumes:
      - ./app:/app
    restart: always