今回の目標:外部からスマホで操作することで家のエアコンをirMagicianを使ってON/OFFをする
これを実現させるためには以下のことが必要要件となります。
1)CuBox-iにirMagicianを接続して動作を確認する
2)外部からのアクセスルートを確保する
3)WebUIからirMagicianを操作できるようにする
前回の記事「(20) 小型赤外線リモコン irMagician をつなぐ」で上の1)が出来ているので、今回は2)と3)を解決していきます。
このパターンができると、遠隔操作系のアプリケーションにいろいろと応用できそうです。
[外部からのアクセスルートを確保する]
外部からiPhoneやスマホでアクセスすることを考えてみましょう。すぐに思いつくのは、Webサーバーを外部からアクセスできるようにするやり方です。取得DNS(あるいはダイナミックDNS)をルーターに設定して、公開するWebサーバーにつなぐというやり方です。セキュリティを考えパスワード保護をかければこれも十分通用するやり方でしょう。この方法は一般的なのであえて掘り下げません。ここでは、せっかくDebian headlessを使っているので、ペタピココラムのMicro-Serverを利用した例としてVPNで接続することに挑戦してみます。SoftEther VPN は iPhone や Android からの VPN 接続を受付けることができます。
SoftEther VPN自体のインストールは、
(18) Debianでmicro-home-serverを作る (CuBox-i & HummingBoard)
を参照してサーバー類をまとめて入れてしまうもよし、今回はVPNだけを使うというならば、先のインストーラーのfunction.shのシェル記述を参考にしてSoftetherVPNをインストールください。
https://ja.softether.org/5-download
最初に告白しますが、実はSoftetherVPNの設定でハマり込みました。注意すべきポイントがあります。Linuxの制限だそうですが、SoftEther VPN Serverをインストールした同じマシンに存在するサーバーへアクセスすることはできません。マニュアルページをよく見るとこのようにありました。
*「Linux および Solaris オペレーティングシステムでは、仮想 HUB (VPN) の内側からローカルブリッジ先のLANカードから LAN への通信は行うことが出来ますが、ローカルブリッジしている LAN カード自体に対して通信することはできません。これは Linux カーネルの制限事項です。」
一般的にVPNサーバーを立てるだけと違い、micro-home-serverでは、VPNもWebサーバーも同じCuBox-iを1台でこなすには工夫が必要となります。設定のあとで「tapデバイス使用時の設定」のところで説明します。
このあとにやるべきこと:
ステップ 1. 管理マネージャを起動してVPNサーバーをインストールしたCuBox-iと接続する
ステップ 2. 仮想HUBを追加し、ローカルブリッジを作成します。
ステップ 3. 外部からのアクセス用にddnsを登録して、ルーターのポートを空けます
ステップ 4. VPN Server 上の L2TP/IPsec VPN 機能を有効にします
ステップ 5. ユーザーアカウントを作り、共通キーをを登録します
ステップ 6. tapデバイス使用時のネットワーク設定の変更
ステップ 7. ローカルブリッジ設定
ステップ 8. iPhone/AndroidからVPNでアクセスする
ステップ 1
それでは諸設定の意味を吟味してVPNに関する勉強をしながら進めていきましょう。
設定にはVPN サーバー管理マネージャというUIが準備されています。同一ネットワーク内にあるWindowsマシンを設定用のコンソールとして利用します。むろん、継続してVPNを利用する場合には監視したり設定の見直し、利用者追加などでずっと使うことになりますが、初期の設定が済んでしまうと日常的にはほぼ使いません。
https://ja.softether.org/5-download
から「SoftEther VPN のダウンロード」を選び。コンポーネントで
SoftEther VPN Server Manager for Windows
を指定します。
今回はVPN サーバー管理マネージャをインストールしたウィンドウズマシンで作業をすすめます。Cubox-iはTeraTermなどでSSH接続して、随時必要な操作ができるようにしておきます。VPN サーバー管理マネージャを立ち上げて設定をしていきます。VPNはいろいろな接続方法に対応するので大変複雑に見えます。たとえ見た目のよいGUIウィンドウを使っても、色々なものが現れるので心が折れてしまいそうになります。今回はiPhone/Androidから、自宅のルーターを介して家のLANに接続されたCuBox-iにSoftEtherVPNが載っていて、同じマシンにあるWebサーバーにアクセスするようにします。これだけに絞って作業を進めます。
まずこんな画面からスタートします。
新しい接続設定をクリックします。
接続設定名を入力します。(設定名は任意の名前でよい)。ホスト名にhome micro serverをインストール時に設定しているローカルIPアドレスを入力します。入力が完了したら、パスワードの欄は入力せずに、OKをクリックします。
次にログイン画面がでるので、そこでユーザー名とパスワードを設定 を入力し、OKをクリックします。
ステップ 2
Bridge 簡易セットアップのウィンドウでは
リモートアクセスVPNサーバー
を選んで(他の□にはチェックをいれない)次へ
仮想HUBの名前をつけ、仮想HUBの管理用パスワードを設定する。(仮想HUBごとに管理者をかえることができるようになっている、今回とりあえず1つだけ作成する)
ステップ 3
ダイナミックDNSホスト名を設定して上記のDNSホスト名に変更するをクリックします。現在の状態を確認して閉じるをクリックします。
DNS鍵は、別に控えておきます。
同じダイナミックDNSホスト名を別の環境で再度使いたき時には、この鍵が必要となります。
ステップ 4
IPsec /L2TP / EtherIP /L2TPv3 サーバー機能の設定画面ではL2TPサーバー機能を有効にする(L2TP over IPsec)にチェックを入れ、IPsec事前共有鍵を設定します。
VPN Azure を無効にする を選択してOK
ステップ 5
ユーザを作成します。ユーザー名・本名・認証時のパスワードを入力します。
作成されたユーザーを確認します。
さらに、ユーザーを新規作成する場合は、新規作成をクリックします。
ステップ 6
この画面から仮想HUBを選択し、仮想HUBの管理をすることができます。暗号化と通信関係の設定を変えることもできます。ここがこれまでの設定で変更や管理する基本的な画面となります。
ローカルブリッジ設定を押し、ローカルブリッジを設定しなおします。
ローカルブリッジ設定の変更
SoftEther VPN サーバ管理マネージャでローカルブリッジをtapデバイスに変更します。
設定済みであるローカルブリッジ設定を削除します。
ローカルブリッジの削除をクリックします。
仮想HUBを選択します。
新しいtapデバイスとのブリッジ接続を選択します。
新しいtapデバイス名を入力します。ここではvlanとします。
入力が完了したら、ローカルブリッジを追加をクリックします。
ステップ 7
tapデバイスの作成
SoftEther VPN Serverをインストールした同じマシンに存在するサーバーへアクセスするためには、SoftEther VPNの設定とDebianのネットワーク設定の変更が必要となります。
1. SoftEther VPN サーバ管理マネージャで、tapデバイスをローカルブリッジさせる
2. Debian起動時に、物理的なネットワークアダプタ(eth0)を仮想ブリッジにブリッジさせるようにする
3. スタートアップスクリプトを修正し、VPNサーバ起動時に、tapデバイスを仮想ブリッジにブリッジさせる
ややこしく聞こえるようですが、tapデバイスというのを作っておいて、VPNとeth0をそこにつないでやると考えればいいということです。
Debian側で、仮想ブリッジ (br0) を追加するために、ネットワーク設定を変更します。
最初に、bridge-utilsパッケージをインストールします。
# apt-get install bridge-utils |
/etc/network/interfaces
を編集して、物理的なネットワークアダプタ(eth0)を仮想ブリッジ(br0)にブリッジさせます。このとき、IPアドレス等の設定は、br0に割り当てし、eth0にはipアドレスを割り当てないようにします。
スタートアップスクリプトファイルを修正して、vpnserverを起動時にtapデバイスを仮想ブリッジへブリッジするように変更します。
------------------------
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet manual
# SoftEtherVPN
auto br0
iface br0 inet static
address 192.168.xx.xx (自分の環境にあわせます)
netmask 255.255.255.0
network 192.168.0.0
broadcast 192.168.0.255
gateway 192.168.xx.xx(自分の環境にあわせます)
# tapデバイスはvpnserverでブリッジさせるので、eth0のみをブリッジする
bridge_ports eth0
bridge_maxwait 10
------------------------
スタートアップスクリプトの変更
/etc/init.d/vpnserver
スタートアップスクリプトファイルを修正して、vpnserverを起動時にtapデバイスを仮想ブリッジへブリッジするように変更します。
------------------------
#!/bin/sh
### BEGIN INIT INFO
# Provides: vpnserver
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: SoftEther VPN
# Description: Start vpnserver daemon SoftEther VPN Server
### END INIT INFO
DAEMON=/usr/local/vpnserver/vpnserver
LOCK=/var/lock/vpnserver
. /lib/lsb/init-functions
test -x $DAEMON || exit 0
case "$1" in
start)
sleep 3
log_daemon_msg "Starting SoftEther VPN Server" "vpnserver"
$DAEMON start >/dev/null 2>&1
touch $LOCK
log_end_msg 0
sleep 3
# SoftEther VPNで追加した仮想tapデバイス名を調べる
tap=`/sbin/ifconfig -a| awk '$1 ~ /^tap/ {print $1}'`
/sbin/brctl addif br0 $tap
;;
stop)
log_daemon_msg "Stopping SoftEther VPN Server" "vpnserver"
$DAEMON stop >/dev/null 2>&1
rm $LOCK
log_end_msg 0
sleep 2
;;
restart)
$DAEMON stop
sleep 2
$DAEMON start
sleep 5
# SoftEther VPNで追加した仮想tapデバイス名を調べる
tap=`/sbin/ifconfig -a| awk '$1 ~ /^tap/ {print $1}'`
/sbin/brctl addif br0 $tap
;;
status)
if [ -e $LOCK ]
then
echo "vpnserver is running."
else
echo "vpnserver is not running."
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
esac
exit 0
------------------------
再起動させて、ブリッジ動作の確認をします。
eth0とtap_vlanがブリッジされていることを確認。
# ifconfig
でbr0, eth0, lo, tap_vlan が表示されていればOKです。
ステップ 8
iPhoneのVPN接続設定
「設定」→「一般」→「VPN」→「VPN構成を追加」
と辿ってでVPN接続できるようにします。
説明: (任意、今回は分かりよくirMagicianにした)
サーバ: ダイナミックDNSで設定したサーバ名(---.softether.net)
アカウント: ユーザ作成画面で作成したユーザとハブ名(ユーザ名@ハブ名)
RSA SecurID : オフ
パスワード: ユーザ作成画面で設定したパスワード
シークレット: ダイナミックDNSで設定したIPsec事前共有鍵
全ての信号を送信: オン
ルーターの設定
ルータのポート開放を行います。
メーカーにより名前が異なりますが、「ポートマッピング」とか「ポート変換」を選びます。L2TP over IPsec機能をのためのポート開放するのは基本的に以下の2つです。
UDPポート: 500
UDPポート: 4500
LAN側IPアドレスにはCuBox-iサーバーに設定の固定IPアドレスを指定します。
iPhoneからVPNアクセスしてみる
「設定」→「一般」→「VPN」で、スイッチをオンにする。
(左の例では、「SqueezeBoxでPandoraを聴く」で紹介した時のUSのVPN接続 StrongVPNも見えていますが、今回の記事には無関係です)
これで外部からのアクセスが確認できました。今回のアプリケーションだけでなくVPN接続ができると外部からの接続全般に使えるので大変便利です。各アプリケーション毎の認証を用意する必要がないこと、Webやアプリを「公開」設定しなくてもよいことなどメリットが大きいので是非チャレンジしてみてください。
[WebからirMagicianを操作できるようにする]
次に、Web上の操作でirMagicianの制御をすることを考えます。
通常Apacheを立ててあれば、そこからCGIとかjavascript経由でハードウェアを制御すればよいことになります。このやり方は一般的なので、ネット上を検索するだけで色々なやり方がすぐにわかることでしょう。今回ここでは、irMagicianを制御するのにpythonでやった流れをうけ、Tornadoというツールを使って実装を試みます。
Tornadoとは、Pythonで書かれたWebフレームワーク/非同期通信ライブラリです。このような特徴があります。
* OpenSource(FriendFeedにより開発された)
* Python2系, Python3系, PyPyで動作
* シングルプロセス、シングルスレッド
* シンプルで高速、スケーラブル
* テンプレートエンジンあり
Tornadoのインストール
Tornadoのインストールはパッケージ管理pipで行います。
$ pip intall tornado |
前回ですでにインストール済みとなっているはずですが、pipを導入してない方はtornadoをダウンロードしてきてインストールします。
$ tar xvzf tornado-3.2.2.tar.gz $ cd tornado-3.2.2 $ python setup.py build
$ sudo python setup.py install |
とりあえずHello, world で動作確認します。
Hello, worldを表示するには、以下のコードを適当なディレクトリで適当な名前(ここではserver.py)を付けて保存して実行します。
ーーーーーーーーーーーーー
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(9005)
tornado.ioloop.IOLoop.instance().start()
ーーーーーーーーーーーーー
起動してみます
$ python server.py |
ターミナル上には何も表示されませんが、この状態でブラウザで
http://IPアドレス:9005
へアクセスすれば、"Hello, world"が表示されます。Tornadoの詳細は、
https://sites.google.com/site/tornadowebja/
を参照してください。テンプレートエンジンの使い方をマスターできると相当実戦でも使えそうです。
さて、前回のirMagicianの動作環境がそのまま残っているとすると、以上で、今回の課題解決のためのの3つの準備が整いました。それでは、本題にはいりましょう。これからやることは、下準備を順に繋げて本来の目的、外からスマホでアクセスしてエアコンをつけてみることに挑戦します。
1.irm.pyを使って、エアコンの自分の好みの温度設定での冷房ON、暖房ON、OFFの3パターンを覚えさせ、JSONファイルを用意します。ここでは簡単のためファイル名を1.json, 2.json, 3.jsonとしました。
2.Tornadoを使って、Web画面にボタンを用意、ボタンを取得してirmからとってきた再生プログラムをもってきて赤外線が出力されるようにします。(手抜きの動作チェック用ですが参考までにソースを下に載せておきます。サンプルでは ./ にserver.py, irm.py, 1.jason, 2.json, 3.jsonをおいています)
3.ローカルPCのWeb画面からアクセスして、動作チェックをします。
4.iPhone/Androidからのアクセスをして動作チェックをします。
./server.py
ーーーーーーーーーーーーー
#!/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import tornado.ioloop
import tornado.web
import sys
import serial
import time
import json
import argparse
import os
here = os.path.abspath(os.path.dirname(__file__))
ir_serial = serial.Serial("/dev/ttyACM0", 9600, timeout = 1)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
def post(self):
self.set_header("Content-Type", "text/plain")
path=self.get_argument("btn") + ".json"
self.write("You put" + self.get_argument("btn"))
playIR(path)
application = tornado.web.Application([
(r"/", MainHandler)
],
template_path=os.path.join(os.getcwd(), "templates"),
static_path=os.path.join(os.getcwd(), "static"),
)
def playIR(path):
if path and os.path.isfile(path):
sys.stdout.write("Playing IR with %s ..." % path)
f = open(path)
data = json.load(f)
f.close()
recNumber = len(data['data'])
rawX = data['data']
ir_serial.write("n,%d\r\n" % recNumber)
ir_serial.readline()
postScale = data['postscale']
ir_serial.write("k,%d\r\n" % postScale)
#time.sleep(1.0)
msg = ir_serial.readline()
sys.stdout.write(msg)
#print msg
for n in range(recNumber):
bank = n / 64
pos = n % 64
if (pos == 0):
ir_serial.write("b,%d\r\n" % bank)
ir_serial.write("w,%d,%d\n\r" % (pos, rawX[n]))
ir_serial.write("p\r\n")
msg = ir_serial.readline()
#print msg
#ir_serial.close()
else:
sys.stdout.write("Playing IR...")
ir_serial.write("p\r\n")
time.sleep(1.0)
msg = ir_serial.readline()
sys.stdout.write(msg)
if __name__ == "__main__":
application.listen(9005)
sys.stdout.write("Server is up ...")
tornado.ioloop.IOLoop.instance().start()
ーーーーーーーーーーーーー
./templates/index.html
ーーーーーーーーーーーーー
<!DOCTYPE html>
<html>
<head>
<title>リモコン</title>
<link rel="stylesheet" href="{{ static_url("style.css") }}"/>
</head>
<body>
<div id="container">
<div id="main">
<form method="post" action="/">
<input type=submit name="btn" value="1" >cooler ON</button>
<input type=submit name="btn" value="2" >warner ON</button>
<input type=submit name="btn" value="3" >OFF</button>
</form>
</div>
</div>
</body>
</html>
ーーーーーーーーーーーーー
./static/style.css
ーーーーーーーーーーーーー
body {
font-family:'Lucida Grande', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', "MS Pゴシック", sans-serif;
width: 80%;
margin: 0 auto;
}
ーーーーーーーーーーーーー
コメントをお書きください