raspberry pi 3B+でテレビ電話システムを構築する
はじめに
この投稿ではサーバーの用意が必要なので、sapislabを使ったやり方を用意しました。
サーバー立てるの厳しい方はこちらをご覧ください。
システム概要
raspberry pi 3B+ によるテレビ電話システム
※記載内容に責任は持てません、全て自己責任でお願いします
概念図
背景
- 妻が夕食の準備中に子供の相手が大変である
- おばあちゃんが一人なので心配である
改善案
- 子供とおばあちゃんが気軽にテレビ電話が出来るようにする
要件
- テレビ電話が出来ること
- 手軽に使えること(煩雑だと結局使われない)
- 費用は抑えること(常套句)
想定シナリオ
- 妻が子供たちを連れて帰宅
- 我が家からリモコンでスイッチオン
- 我が家とおばあちゃん家のテレビが自動で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すれば大丈夫だと思う(試してない)
状態遷移図
開始処理のシーケンス図
終了処理のシーケンス図
APIサーバーの機能(超単純)
- 現在の状態を取得できる
- 状態を変更できる
- 接続すべきURLを取得できる
- セキュリティ関連
- 必要に応じてapiキーによる制御
やり方
raspberry pi 3(caller, receiverともに)
- ネットワーク設定(wifiとか)
- スクリーンセーバーの無効化
- cec-clientのインストール(HDMI制御に使う)
- $ sudo apt-get install cec-utils
- 接続用エージェントプログラムの配置
- 「https://github.com/miya15/vtc-agent」に置きました
- piユーザー用に記述されている点に注意
raspberry pi 3(receiverのみ)
raspberry pi 3(callerのみ)
- AB Shutter3 で任意のシェルが叩ける準備(下記の記事を参考に準備する)
- ON用のボタンが押されたら「vtc-exec-caller-on.sh」を叩くようにする
- OFF用のボタンが押されたら「vtc-exec-caller-off.sh」を叩くようにする
- 注意:どちらのシェルスクリプトにもAPIサーバーのURLとAPIキーを記載する場所があるので適宜入れておくこと
APIサーバー
- docker, docker-compose が入っていなければインストール
- プログラム配置
- 「https://github.com/miya15/vtc-api-server」に置きました
- docker-compose.ymlがあるディレクトリに移動して「$ docker-compose up -d」で起動する
- apiキーなんて要らないという勇者は「@require_apikey()」を削除すればチェックされません
- raspberry pi 側から疎通確認(ドメインとapiキーを適宜置き換えて)
- $ curl https://example.com/api/echo
- "hello"がレスポンスに含まれること
- $ curl https://example.com/api/url -v -X GET -H "Content-Type: application/json" -H "x-api-key:YourAPIKey"
- redisで設定したurlがレスポンスに含まれること
- $ curl https://example.com/api/echo
- 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"
各種プログラム
- APIサーバーのプログラム
- 接続用エージェントプログラム
注意事項
- 電化製品なので漏電や火災に気を付けましょう
- 各種OSやミドルウェアのバージョンを新しく保つよう心がけましょう
- raspberry pi もパソコンですからBOT化しないよう気を付ける
- テレビによってはHDMIの制御が効かないものがあります、ご注意ください
- まずはraspberry piを1台だけ買って双方のテレビに繋いでコマンド投げて試す方が良い
- 接続先URLをAPIサーバーから取得するようになっているので気になる方は直接プログラムに埋め込んだ方がより安全です
- URLが変更できるようになっているのはルームIDを変更したり別のサービスに切り替えるのが手軽に出来るようにする為です
今後の発展
- 状態遷移はjson形式で渡しているので遷移を増やすと面白いかもしれない
- 定期的にAIで在席・不在を判断できるとその後のアクションにも繋げられるか
運用中に発生した問題点について
- receiver側でHDMIの制御がうまくいかない事があった
- 実際に繋がると子供(5歳男児)は映っている自分に興奮して踊りだし、おばあちゃんとの会話にならない(苦笑いしてるというありさま)
- 完全に想定外
メモ
- APIサーバーはAWSのapi gatewayとlambdaとDynamoDBの組み合わせでも問題無いと思うが今回はパス(悪意のあるユーザーに大量に叩かれたら嫌なだけ)
- Herokuの無料プランでも使えるかもしれない
- ちょっと試したけどredis使うならクレジットカード登録が必要ですね
- 今回はraspberry piを使う事が前提にあったが、intel系のスティックPCとかでskypeを使う方式としても使えるかもしれない(skypeには自動応答機能があるので、コマンドラインからコール出来れば完成したようなもの)
- プログラムが最適化されていない点はご容赦ください
- バグなどありましたらコメント頂けるとありがたいです
- 取り敢えず1ヵ月ほど運用してみましたがうまく動いています(思ったほど活用されてないのが残念ですが・・・)
redmineでPlantUMLを表示する際にサーバー側で画像を生成させる方法
概要
PlantUMLは便利ですが画像生成をするアプローチに苦慮します。
記述している内容を秘匿する必要がある場合は尚更です。
今回はredmineのプラグインplantuml-redmine-macroに手を加えて社内サーバーで画像を生成させ、それをHTTPレスポンスに含めるやり方を説明したいと思います。
やり方
まずはプラグインのご紹介。
このままだと公式サーバーの方に情報が流れてしまうので社内サーバーを立てます。
今回も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
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のサンプルコードを参考にさせて頂きました
docker-compose一式
必要なプログラムもdocker-compose.ymlも下記githubに置いてあります。
使い方もREADME.mdを合わせてごらんください。
個別ファイル
最新は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