2015年11月28日土曜日

Kinect + Raspberry Pi 2 で監視カメラを作る(3)

外からラズパイにログインするための準備

前回でライブラリとベースとするソースは決めたのでこれからはごりごりコードを書いていきたいのだが、いかんせん家にいると子守と Fallout4 を並列にこなさなければならないのでその合間にコードを書くのはなかなかヘビーだ。

そこで少しでも作業が進むように外からスマフォなりタブレットなりで SSH でログインしてコーディングやその他の作業が出来る環境を整える。

何はともあれ、ラズパイを外部に公開するので、まずはそれなりにセキュリティを強化していく。

  • デフォルトユーザーのパスワード変更
    > passwd
  • 外部ログイン用グループ(developper)の作成
    > sudo groupadd developper
  • 外部ログイン用ユーザー(ruser)の作成
    > sudo useradd -c "remote user" -g developper -m -N ruser
  • 作成したユーザーにパスワードを設定
    > sudo passwd ruser
  • デフォルトユーザーの外部ネットワークからのログインを禁止
    /etc/security/access.conf に以下の行を追加
        - : pi : ALL EXCEPT 192.168.1.
これでWANからは先ほど作成したruserでしかログインできなくなる。
ルートの権限がほしい場合は、ruserでログインした後で、
> su - pi
でユーザーを切り替えるので両方のパスワードを知らないとroot権限を取得できないことになる。

さらに、
  • pi を先ほど作成したdevelopperグループに追加
    > sudo gpasswd -a pi developer
        ("usermod -G" は罠なので使ってはいけない)
  • 共有ディレクトリの作成
    > sudo mkdir /home/developer
    > sudo chown pi /home/developer
    > sudo chgrp developper /home/developper
  • 共有ディレクトリにSGIDを設定して異なるユーザーがファイルを作成しても同じグループになるようにする。
    > sudo chmod 2770 /home/developer/
  • デフォルトでグループに書き込み権限を与える
    /etc/profile に以下の行を追加
      umask 002
これで、外からは ruser, 家では pi でログインしても同じように作業が出来る。

そとからラズパイにログイン

グローバルIPをもらえる環境なら DDNS なりなんなり使えば良いが、我が家のマンションネットではそれが難しそうなのでホスティングサービスを利用して踏み台となる公開サーバーを用意して、そこに逆方向トンネルを掘って繋げる作戦で行く。

AmazonECにアカウント作成

この手のサービスは幾つかあるが、やっぱり無料で使えるAmazonECを選択した。
  • Ubuntuの仮想マシンを作成
  • 現在のサブネットからポート22(SSH)にアクセスできるようにセキュリティルールを追加
  • ssh でログイン
    Ubuntuの場合、初期ユーザー名はUbuntu
    Rloginの場合はSSH Identify Keyに仮想マシン作成時に生成したキーファイルを指定
  • ログインできたら早速 /etc/ssh/sshd_confに以下の一行を追加してトンネルを可能にする
    GatewayPorts yes
  • 次にラズパイにキーファイルをコピーしてアクセス権を変更
    > chmod 400 KeyFile.pem
  • キーファイルを使ってトンネルを掘る
    > ssh -i KeyFile.pem -f -N -R :10022:localhost:22 user@hostname
    ここでは仮想マシンの10022ポートをラズパイの22(ssh)ポートに転送する
  • EC2 ManagementConsoleに戻って先ほどの10022ポートをグローバルに通すようにセキュリティルールを変更
  • ポートに10022を指定して仮想マシンにsshをかけると手元のラズパイにログインできる。
設定が確認できたらautosshを利用して起動時に自動で接続させる
> sudo apt-get install autossh

一旦ルートになってSSHで仮想マシンにログイン
> sudu su -
> ssh -i /home/pi/KeyFile.pem user@hostname
※一見無意味な儀式だが、ルートのknown_hostsに登録するために必要となる

/etc/rc.localに以下の行を追加
sleep 10
/usr/bin/autossh -i /home/pi/KeyFile.pem -f -N -R :10022:localhost:22 user@hostname
※起動直後はネットワークの接続が完了していないことがあるので、とりあえず10秒スリープさせる

以降はラズパイを起動すると自動で仮想マシンにトンネルを作成してくれるので、そこからアクセスできるようになる。

スマフォ用のSSHクライアントは色々あるのでご自由に
どちらにしてもスマフォからがっつりコーディングするのは厳しいが、修正や簡単な動作確認用のコードの追加ならなんとか

2015年11月25日水曜日

Kinect + Raspberry Pi 2 で監視カメラを作る(2)

OpenKinect (libfreenect) のインストール

お手軽に使えそうなラズパイ用のアプリを探してみたのだが、さっぱり見つからないので腹を据えて自分でプログラムすることにした。

ラズパイでも使える Kinect 用のAPIとして libfreenect があるので、まずはこれを導入する。
ただし、サンプルプログラムのビルドに難があるようなので(参考)サンプルプログラムをビルドしないように cmake のオプションを指定する必要がある。

> sudo apt-get install libusb-dev
> sudo apt-get install freeglut3-dev ※example をビルドしないなら要らないかも
> git clone https://github.com/OpenKinect/libfreenect
> cd libfreenect
> mkdir build
> cd build
> cmake -L .. -DBUILD_REDIST_PACKAGE=OFF -DBUILD_EXAMPLES=OFF
> make
> sudo make install

※ 参考ページだと CMakeLists.txt を編集しているが、cmake のオプションで指定した方が手っ取り早い。今回は再配布の予定も無いのでついでにそのオプションも追加した。

これでKinect 用のライブラリとドライバがインストールされるので、Kinect を差し直すか、ラズパイを再起動すれば新しいドライバが認識される(はず)。

動作確認その1

動作確認のために先ほどの参考ページにサンプルソースが貼ってあったので、コピペして Make した。
途中、sleep が見つからないと怒られたのでソースに"#include <unistd.h>"を追加したら通った。

ためしに実行してみる。
> sudo env LD_LIBRARRY_PATH=/usr/local/lib ./tiltdemo

libfreenect のビルドとインストールが上手くいっていればこれで Kinect センサーがウィーンと良いながら上下に動いてくれるはず。

毎回環境変数を設定するのも面倒なので/usr/local/lib をパスに追加しておく。
> sudo su - root
> echo /usr/local/lib > /etc/ld.so.conf.d/usr-local.conf
> ldconfig
> exit

動作確認その2

libfreenect の example を眺めてみると幾つか動かせそうなものがあったので先ほど作成したサンプルの Makefile を参考にコンパイルしてみた。

> gcc -o camtest -I/usr/local/include/libfreenect camtest.c -lfreenect
> gcc -o tiltdemo -I/usr/local/include/libfreenect tiltdemo.c -lfreenect -lfreenect_sync

両方とも sudo を付けて動かすと無事に動いてくれた。

なお、'#include "libfreenect.h"' を '#include <libfreenect/libfreenect.h>' に変えると -I... を付けなくてもコンパイルできるようになる。

camtest が Kinect の RGB カメラと Depth センサーのデータを繰り返し読み込むデモのようなので、これをベースに自前のアプリを作っていこうと思う。

2015年11月19日木曜日

Kinect + Raspberry Pi 2 で監視カメラを作る(1)

XBOX360のキネクトが余っていたので、ラズパイと組み合わせて寝ている我が子を見守るための監視カメラを作れないかやってみた。

OSのインストール

ラズパイを触るのは初めてなので、まずはOSをインストールするところから。
  • 公式ページに行って素直にNOOBSをダウンロード
  • Zipファイルを展開
  • 手持ちのMicroSDカードにコピー
    ※公式ページにはフォーマットツールをダウンロードしろとあったが、エクスプローラーから普通にFAT32でフォーマットしたら普通に使えた
  • Raspbianをインストール
かなりお手軽にインストールできた。
いまどきはISO焼き込んだりextでフォーマットしたりしなくて良いのね。

WiFi設定

あまり深く考えずに、ラズパイを購入したときにAmazonでおすすめされたBuffaroのWLI-UC-GNMを一緒に購入
相性が悪ければ最悪ドライバのビルドとか必要かなと思ったが、挿すだけで何もしなくても認識してくれた。ありがとうAmazonさん。
  • WPAの設定
    > sudo su -
    > cp /etc/wpa_supplicant/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf.orig
    > wpa_passphrase SSID PASS_PHRASE >> /etc/wpa_supplicant/wpa_supplicant.conf
    > ifdown wlan0
    > ifup wlan0
    > exit
  • 無線ルーターで固定アドレスに設定
  • Windows用のSSHクライアントとしてRLoginを落としてPCから接続
これで電源だけ繋げば後はリモートで作業できる。

RDP設定

基本はSSHで作業するつもりだが、画像確認などで画面飛ばせた方が便利かと思って一応RDPも設定しておく。

> sudo apt-get install xrdp

PC(Windows)側からはリモートデスクトップで繋げればOK

Kinectを繋ぐ

やるまえに色々調べていたが、結局USBに挿したら何もしなくても認識してくれた。
/dev/video0 が出来ているのを確認。

camoramaで表示

念のため、リモートで配信する前にリモートデスクトップで繋いでローカルできちんと見えるか確認。
> sudo apt-get install camorama
> camorama -d /dev/video0

ストリーミング

  • motionをインストール
      > sudo apt-get install motion
  • /etc/motion/motion.confを編集
      daemon off → on
  • /etc/default/motionを編集
      start_motion_daemon=no → yes
  • 自動起動設定
      update-rc.d motion defaults
で、再起動してPCのブラウザで先ほど設定したIPアドレス:8080にアクセスするとコマ送り状態だがキネクトカメラの画像が見える。
ただ、Chromeからは見えたが、Edgeではファイルの無限ダウンロードになってしまい正常に見えなかった。

一応これでお手軽に最低限監視カメラとしての機能は実現できたが、現状では
  • チルトが出来ない
  • ノーマルカメラでは暗い部屋ではほとんど見えない
と、Kinectの機能を全く生かせていないので、この辺をもうちょっとなんとかしていきたい。

2015年10月15日木曜日

赤城を作る

ハセガワの1/700赤城フルハルモデルをディティールアップパーツと一緒に購入。

武蔵の時のように作りながら記事を書く時間と根性がなかったので完成形だけ。






高い金出してエッチングパーツを買ったけど、結局飛行甲板裏のところまで作り込む根性はなかったので飛行甲板はキット標準の方を利用。
もともとのキットの出来が良いのでエッチングパーツがなくても十分見栄えしそうだけど、やっぱり手すりがあるだけでも近寄って見たときの印象は良い。

やっぱり戦艦に比べて構造がかなり複雑なので、作るのは難しい。
特に後甲板部分は時間と金が腐るほどあるならもう一度やり直したい気分。

2015年8月30日日曜日

C++ で C# の delegate っぽいものも作ってみた

こないだ event 風のものを作ってみたけど、もう一ひねりしたら delegate っぽいものも作れた。

template<typename T>
class Delegate;

template<typename R, typename... A>
class Delegate<R(A...)> {
private:
    std::function<R(A...)> mFunc;
public:
    Delegate(std::function<R(A...)> func) : mFunc(func) {}
    template<typename C, typename T>
    Delegate(C* pObj, T func) {
        mFunc = [=](A... args) { std::mem_fn(func)(pObj, args...); };
    }
public:
    void operator()(A... args) {
        mFunc(std::forward<A>(args)...); // 修正
    }
};

event と組み合わせるとこんな感じで使える。

class EventManager {
public:
    typedef Delegate<void(const std::string&)> TestEventHndlr;
    Event<TestEventHndlr> TestEvent;
    void InvokeTestEvent() {
        TestEvent("This is TestEvent. > ");
    }
};

class EventHandler {
public:
    void Hndlr(const std::string& msg) {
        std::cout << msg << "I'm a member function." << std::endl;
    }
};

void print_msg(const std::string& msg) {
    std::cout << msg << "I'm a static function." << std::endl;
}

int main()
{
    EventManager manager;
    EventHandler hndlr;
    manager.TestEvent += EventManager::TestEventHndlr([](const std::string& msg) {
        std::cout << msg << "I'm a lambda function." << std::endl;
    });
    manager.TestEvent += EventManager::TestEventHndlr(print_msg);
    manager.TestEvent += EventManager::TestEventHndlr(&hndlr, &EventHandler::Hndlr);
    manager.InvokeTestEvent();
    return 0;
}

※追記
unique_ptr を使おうとしたらハマったので Delegate を一部修正。
Event の方は複数のハンドラを登録できるようにしているのでもともと unique_ptr は適さない。
どうしても使いたければハンドラを一つしか登録できないイベントクラスを別途作る必要があるが、それよりはイベントハンドラ側にメモリ管理をさせないようにデータ構造を見直した方が健全かなぁ。。。

2015年8月23日日曜日

C++0x のラムダ式と可変長テンプレートで C# っぽいイベント処理

世の中便利になったものね~。
これでコールバックを多用した非同期処理がだいぶ書きやすくなりそう。

どうしてもメンバ関数は一旦ラムダ式で包んであげる必要があるので、デリゲートの仕組みも作れると完璧なんだが・・・

#include <iostream>
#include <list>
#include <functional>
#include <string>

template<typename T>
class Event {
private:
    std::list<T> mHndlrs;
public:
    void operator+=(T hndlr) {
        mHndlrs.push_back(hndlr);
    }
    template<typename... A>
    void operator()(A... args) {
        for (auto it = mHndlrs.begin(); it != mHndlrs.end(); it++) {
            (*it)(args...);
        }
    }
};

class EventManager {
public:
    Event<std::function<void(const std::string&)> > TestEvent;
    void InvokeTestEvent() {
        TestEvent("This is TestEvent. > ");
    }
};

class EventHandler {
public:
    void Hndlr(const std::string& msg) {
       std::cout << msg << "I'm a member function." << std::endl;
    }
};

void print_msg(const std::string& msg) {
    std::cout << msg << "I'm a static function." << std::endl;
}

int main()
{
    EventManager manager;
    EventHandler hndlr;
    manager.TestEvent += [](const std::string& msg) {
        std::cout << msg << "I'm a lambda function." << std::endl;
    };
    manager.TestEvent += print_msg;
    manager.TestEvent += [&](const std::string& msg) {
       hndlr.Hndlr(msg);
    };
    manager.InvokeTestEvent();
    return 0;
}

2015年8月2日日曜日

JavaScript で Sudoku を解いてみる (3)

前回までで、問題と条件が記述できたので、後は例によって解けば良い。

今回はモナドを利用したおかげで条件の記述がそのまま条件をチェックするコードになるので、ソルバは非常にシンプルに書ける。

        function solve(question, conditions, iter) {
            var result;
            if(iter === undefined) iter = 0;
            else if(iter >= question.length) return;
            var answer = [];
            question[iter].selectEach(function() {
                result = conditions();
                if(result === undefined)
                    answer = answer.concat(solve(question, conditions, iter+1));
                else if(result === true) {
                    answer.push(question.map(function(tile) { return tile.get(); }));
                }
            });
            return answer;
        }

        var Question = Cells.reduce(function(prev, row) {
            return prev.concat(row);
        }, []);

        var Answer = solve(Question, Condition);

ここまでシンプルに書けると、もはやソルバは要らないんじゃないかと思えてきて、問題に吸収させて、問題生成時に問題に対応するソルバを生成するようにもしてみた。

        function Question(items) {
            this.Solve = items.reduce(function(prev, item) {
                return function(condition) {
                    var answer = [];
                    item.selectEach(function() {
                        result = condition();
                        if(result === undefined)
                            answer = answer.concat(prev(condition));
                        else if(result === true) {
                            answer.push(items.map(function(tile) { return tile.get(); }));
                        }
                    });
                    return answer;
                }
            }, function() { return []; });
        }

        var Answer = (new Question(Cells.reduce(function(prev, row) {
            return prev.concat(row);
        }))).Solve(Condition);

実際に解いてみると、簡単な問題なら一瞬で解けるが、難易度が上がると試行回数が跳ね上がってむちゃくちゃ時間がかかるようになる。
この辺はスクリプトの限界なのか、それとも工夫すればもっと速くなるのか。。。