2015年11月20日金曜日

CentOS7 + AWS で docker-swarm を試してみた

概要

docker-swarm は複数の docker-engine を 1 つの docker-engine として扱うことができるクラスタリングツールです
コンテナがいっぱいになってスケールしたいとき docker-engine をクラスタに追加することで簡単にコンテナをスケールすることができるようになります
今回は例のごとく Getting Started を試してみたので紹介します

環境

  • CentOS7 64bit
  • docker-machine 0.5.0
  • docker-engine 1.8.3

事前準備

docker-swarm は docker-machine の機能を使っているので事前に docker-machine をインストールしてください
http://kakakikikeke.blogspot.jp/2015/11/try-docker-machine-with-aws.html

クラスタをディスカバリするためのトークンを生成する

docker-swarm で生成するクラスタはトークンを使って、どのクラスタかを管理します
まずはトークンを作成するために 1 台マシンを構築します

  • docker-machine create --driver amazonec2 --amazonec2-access-key [Your Access Key] --amazonec2-secret-key [Your Secret Key] --amazonec2-vpc-id vpc-xxxxxxxx --amazonec2-region ap-northeast-1 --amazonec2-zone c token-machine

この辺のコマンドは前回の docker-machine の紹介で記載したコマンドと同じです
10 分くらいでインスタンスが立ち上げるので待ちましょう
作成できたら、マシンを確認後、環境変数を読み込みます

  • docker-machine ls
  • eval "$(docker-machine env token-machine)"

そしてこのマシン上で 1 つコンテナを立ち上げます
コンテナの元イメージになるのは DockerHub で公開されている swarm というイメージを使います

  • docker run swarm create

これを実行するとイメージのダウンロードが始まり、その後コンテナが起動され、以下のようにコンソールに出力されます

Unable to find image ‘swarm:latest’ locally
latest: Pulling from library/swarm
2bc79aec8ea0: Pull complete
dc2fb86a875a: Pull complete
435e648d0f23: Pull complete
e16042a92d05: Pull complete
045bd7b00b5b: Pull complete
3caea1253d76: Pull complete
2b4c55187a27: Pull complete
6b40fe7724bd: Pull complete
Digest: sha256:51a30269d3f3aaa04f744280e3c118aea032f6df85b49819aee29d379ac313b5
Status: Downloaded newer image for swarm:latest
6c56e07a671372421a6724ce79047d2f

この出力の最後の文字列「6c56e07a671372421a6724ce79047d2f」これをトークンとして使います

クラスタ内でのマスターノードを作成する

トークンが取得できたらクラスタを構築していきます
docker-swarm のクラスタの世界ではマスターと、それ以外のエージェントノードで構成されます
まずはマスターノードを作成する必要があります

  • docker-machine create --driver amazonec2 --amazonec2-access-key [Your Access Key] --amazonec2-secret-key [Your Secret Key] --amazonec2-vpc-id vpc-xxxxxxxx --amazonec2-region ap-northeast-1 --amazonec2-zone c --swarm --swarm-master --swarm-discovery token://6c56e07a671372421a6724ce79047d2f swarm-master

コマンドが長くなってきました
前半のドライバの指定やアクセスキーの指定は同じです
違うのは --swarm 以降のコマンドでこれを指定することでクラスタとして作成することができます
--swarm-master オプションを付けることでマスターとしてノードを立ち上げることができます
そして、先ほど作成したトークンを --swarm-discovery オプションを使って指定します

無事ノードが作成されれば完了です

エージェントノードを追加する

マスターノードができたらエージェントノードを追加していきます
クラスタをスケールさせたいときにはこのエージェントノードを追加していきます

  • docker-machine create --driver amazonec2 --amazonec2-access-key [Your Access Key] --amazonec2-secret-key [Your Secret Key] --amazonec2-vpc-id vpc-xxxxxxxx --amazonec2-region ap-northeast-1 --amazonec2-zone c --swarm --swarm-discovery token://6c56e07a671372421a6724ce79047d2f agent01

コマンドは先程のコマンドとほぼ同じです
--swarm-master の指定がないのと作成するノード名がことなるだけです
こんな感じで agent02 まで作成しましょう

作成できたノードを確認する

  • docker-machine ls
NAME          ACTIVE   DRIVER      STATE     URL                        SWARM
token-machine *        amazonec2   Running   tcp://xx.xx.xx.xx:2376    
swarm-master  -        amazonec2   Running   tcp://xx.xx.xx.xx:2376     swarm-master (master)
agent01       -        amazonec2   Running   tcp://xx.xx.xx.xx:2376     swarm-master
agent01       -        amazonec2   Running   tcp://xx.xx.xx.xx:2376     swarm-master

でノードの一覧を確認することができます
SWARMの欄にマスターノードの名前が記載されていることが確認できると思います
また、クラスタ内のマスターノードには (master) があると思います

docker-machine コマンドの他に docker-info コマンドでもクラスタの状況を確認することができます

クラスタ内にコンテナを作成する

まずは docker コマンドでこのコンテナを操作できるようにします

  • eval "$(docker-machine env --swarm swarm-master)"

env コマンドで環境を表示させ、eval で評価します
これで docker コマンドをコンテナ内で使うことができます
あとはいつも通り docker run すればマスターノードが適当なエージェントノード内にコンテナを作成してくれます

  • docker run hello-world
  • docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                                   NAMES
103e4ed46f78        hello-world         "/hello"                 2 minutes ago       Exited (0) 2 minutes ago                                           agent01/drunk_kalam

最後に

紹介は以上です

ノードを増やしてスケールさせたい場合は docker-machine create して、ノードを削除したい場合は docker-machine rm すれば OK です
docker-machine rm でマスターノードを指定するとクラスタがおかしくなるので注意してください

コンテナの数が数百や数千を超えるような場合には非常に便利な機能だと思いました
コンテナの数やコンテナのリソース消費を監視してキャパシティプランニングまでやってくれると非常に嬉しいなと思いました
いまマシンが足りないから自動で EC2 上にマシンを構築してクラスタに追加しましたとか
さすがに現状だとそこまではやってくれないっぽいです

2015年11月19日木曜日

Redis でバイナリファイルを保存する方法

概要

Redis で PNG 画像のようなバイナリファイルを保存する方法を紹介します
今回は特にプログラムから保存するのではなく、redis-cli を使ってやってみます

環境

  • Mac OS X 10.10.5
  • Redis 3.0.2

バイナリデータの保存

  • redis-cli -x HSET some_key image_binary <image.png

画像データを保存しています
HSET を使って保存しており some_key という key の image_binary というフィールドの値として画像を保存しています
画像情報はパスやファイル名を指定するのではなく、ファイルをリダイレクトで入力してあげます
-x を使うことで最後のパラメータを標準入力から受け取ることができるようになります

バイナリデータの取得

  • redis-cli --raw HGET some_key image_binary > image_new.png

取得する場合は HGET で key とフィールド名を指定します
オプションに --raw を指定する必要があります
これを指定することでマルチバイト文字 (バイナリ) を扱えるようになります
あとは結果を画像ファイルにリダイレクトしてあげれば OK です

取得した画像がちゃんと表示されるかプレビュー等で確認するといいと思います

参考サイト

2015年11月17日火曜日

nrf51822 で Real Time Clock を使って起動時間を取得する方法

概要

nrf51822 上で起動時間を取得するために RTC (RealTimeClock) の仕組みを使ってみました
RTC の値はデバイスの電源の ON/OFF でリセットされる値になります

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0

実装方法

nRF51 DK に含まれる App Timer (以下タイマー) という機能を使って実現します

nRF51 SDK に含まれているサンプルを見るといろいろなサンプルでこのタイマーを使っているので基本はそれをベースにすれば OK です
今回はバッテリーレベル用のタイマーを定義して、それを例に
流れを説明していきます

初期化

まず、タイマーを使うために APP_TIMER_INIT というマクロをコールする必要があります
サンプルではだいたいこんな感じで実装されていると思います

APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);

基本はこれで問題ないです

タイマーの生成

次にタイマーを生成します
生成するには app_timer_create をコールします

uint32_t err_code;
err_code = app_timer_create(&m_battery_timer_id, APP_TIMER_MODE_REPEATED, battery_level_meas_timeout_handler);
APP_ERROR_CHECK(err_code);
static app_timer_id_t m_battery_timer_id;

app_timer_id_t は typedef されている型で実体は uint32_t になります
この ID に対するタイマーを生成していることになります
また、 battery_level_meas_timeout_handler ですがこれは関数になっており、タイマーがタイムアウトしたときにコールされる関数を指定できます
この辺もサンプルを見ると流れが把握できると思いますが、この関数内でバッテリーレベルの再取得を実施し Characteristics の value の更新を行っています

タイマーが生成できたらタイマーをスタートします

タイマーを開始する

タイマーを開始するには app_timer_start をコールします

uint32_t err_code;
err_code = app_timer_start(m_battery_timer_id, BATTERY_LEVEL_MEAS_INTERVAL, NULL);
APP_ERROR_CHECK(err_code);

これをコールしたタイミングから RTC によるカウントが開始されます

現在のカウントを取得する

タイマーを開始したら、現在のカウントを取得してみます
現在のカウントを取得するには app_timer_cnt_get をコールします

uint32_t p_ticks;
app_timer_cnt_get(&p_ticks);
printf("%d \r\n", p_ticks);

こんな感じでカウントしている値を取得できます

ここでちょっとはまったのは app_timer_cnt_get の返り値を参照してもずっと 0 でした
リファレンスを見たのですが、この関数の返り値は正常に取得できたかのフラグを return しているだけなので常に 0 が返っていました
関数側では、受け取った引数のポインタに対して書き込みを行っているので、引数として渡した変数を参照する必要がありました

で実際にこの値を定期的に出力してみたのですが、値を見ると 1, 2, 3, … のような秒数を刻む値ではなく 3256, 6785, 125678, … のようにガンガン値が増えていました
ちょっとこの値に説明します

カウント値について

以下もしかすると、間違いがあるかもしれません

この値について目視でどの程度増えているのか計測してみたのですが、だいたい 30 秒で 1,000,000 カウント増えている感じでした
なので 1 秒間で約 33,000 ほど増えていることになります

何かそれっぽい値を定義している箇所がないか調べてみたのですが、APP_TIMER_CLOCK_FREQ というマクロが定義されておりこの値が 32768 になっていました
おそらくですが 1 秒間にこの APP_TIMER_CLOCK_FREQ の値だけ増加しており、app_timer_cnt_get ではそれが取得できているのだと思います
なので、時間を取得したいのであれば、取得した値を APP_TIMER_CLOCK_FREQ で割り算すればそれっぽい起動時間が取得できると思います

また、値の最大値なのですが、約 16, 800,000 くらいになるとまた 0 にリセットされてカウントが始まりました
( 16, 800, 000 / 33,000 = 510 (sec) = 8 min 30 sec )
なので何回リセットされたかどうかも管理しないとダメそうな感じです
最大値を伸ばす方法とリセットされた回数を取得できる方法があるかどうかは、すいません、調査しきれていません、、、

ちょっとこの辺が曖昧な記載なのは、そこまで深く調べていないので自信がないためです
間違いがや追加の情報あればご指摘いただけると助かります

最後に

とりあえず nrf51822 で起動時間っぽい値を取得できるようになりました
完全に使うためにはもう少し実装が必要そうですが、ベースとなる呼び出し方は理解できたと思います

10分くらいでリセットされるタイマーだとちょっと管理が大変なので、半日分くらいは管理できるようにしたいなと思っています

2015年11月16日月曜日

Jekyll に theme を適用する方法

概要

Jekyll のデザインには様々なテーマが公開されており、これを使って簡単にデザインをカスタマイズすることができます
今回はテーマを使って jekyll build する方法を紹介します

環境

  • Mac OS X 10.10.5
  • Jekyll 2.5.3

テーマを適用してみる

http://jekyllthemes.org/ から好きなテーマを選択してください
今回は Jekyll Now というテーマを使ってみます

  • mkdir -p jekyll/work
  • cd jekyll/work
  • git clone https://github.com/barryclark/jekyll-now.git
  • gem install jekyll-sitemap
  • cd jekyll-now
  • jekyll server

で起動まで実施します
途中サイトマップを表示するためのプラグインをインストールしています
jekyll server を実行すると localhost:4000 で LISTEN するのでブラウザでアクセスしてみましょう

今回のテーマだと以下のようなサイトが表示されると思います
jekyll-apply-theme.png

ちょっとカスタマイズする

名前を変更する

  • emacs jekyll/work/jekyll-now/_config.yml

name: Your Name

を編集して再度 jekyll server すれば変更できます

記事をポストする

  • cd jekyll/work/jekyll-now
  • cp _posts/2014-3-3-Hello-World.md _posts/2015-11-16-sample-post.md
  • emacs _posts/2015-11-16-sample-post.md

jekyll server を実行すれば (内容は特に変更しなくても) 記事が増えていることが確認できると思います
あとはこんな感じで markdown ファイルを増やしていけば OK です

ビルドする

サイトなどで公開するための HTML ファイルを生成します

  • cd jekyll/work/jekyll-now
  • jekyll server

_site というディレクトリ配下に HTML や CSS が作成されるのでそれをそのまま Web サーバの DocumentRoot に配置すれば OK です

最後に

既存の Jekyll に対して適用するやり方は今回紹介しませんでしたが、おそらく _posts 配下にある .md ファイルを一旦退避し、テーマをダウンロード後に _posts ファイルに移動すれば適用できるはずです
プラグインを使って記載されている markdown やレイアウトがある場合はプラグインの再インストールも必要になると思います

特にデザインを自分でゴリゴリ作成したいということでなければ、テーマをベースに作成するといいと思います
レイアウトや 404 ファイルなどサイトの公開に必要なファイルは一通りそろっている状態なので、これをベースにカスタマイズしたほうがいいと思います

自分で作成したテーマはもちろん jekyllthemes.org で公開することもできます

あと Jekyll 3.0 が 2015/10/26 に正式にリリースされましたが、 3.0 だともしかしたらエラーになるテーマがあるかもしれません

2015年11月12日木曜日

CentOS7 + AWS で docker-machine を試してみた

概要

docker-machine は仮想マシン上に docker-engine が動作する環境を構築することができるツールです
今回は CentOS7 上の docker-machine から AWS の EC2 インスタンス上に docker 環境を構築してみます

環境

  • CentOS7 64bit
  • docker-machine 0.5.0
  • docker-engine 1.8.3

docker-machine インストール

事前に docker-engine のインストールを行っておいてください

  • mkdir ~/mydocker.machine
  • cd ~/mydocker.machine
  • curl -L https://github.com/docker/machine/releases/download/v0.5.0/docker-machine_linux-amd64.zip >machine.zip
  • unzip machine.zip
  • rm machine.zip
  • mv docker-machine* /usr/local/bin

docker-machine -v でバージョンが表示されればインストール完了です

AWS 上での準備

API でもマネージメントコンソールでもどちらでも OK です

VPC およびサブネットの作成

マネージメントコンソールにログインしたら

サービス > VPC -> VPCウィザードの起動

と実行すれば VPC とそれに紐づくサブネットを一気に作成できます
create_vpc_and_subnet.png

VPC を作成したリージョンと VPCID をメモしておいてください
サブネットを作成したゾーンもメモしておいてください

API をコールすることになるので、アカウントが持つアクセスキーとシークレットキーを用意してください

docker-machine の実行

では、 AWS 上にインスタンスを作成し docker-engine が動く環境を構築します

  • docker-machine create --driver amazonec2 --amazonec2-access-key [Your Access Key] --amazonec2-secret-key [Your Secret Key] --amazonec2-vpc-id vpc-xxxxxxxx --amazonec2-region ap-northeast-1 --amazonec2-zone c aws01

入力しなければいけないのは [Your Access Key], [Your Secret Key], vpc-xxxxxxxx の部分です
VPC およびサブネットを作成したリージョンとゾーンが異なる場合は ap-northeast-1 と c の部分も書き換えてください

上記の場合は ap-northeast-1 リージョンにある vpc-xxxxxxxx 内のサブネットに aws01 というインスタンスを作成します
作成されるインスタンスのタイプは t2.micro になります

しばらく待っていると docker-engine がインストールされたインスタンスが起動します

Running pre-create checks…
Creating machine…
Waiting for machine to be running, this may take a few minutes…
Machine is running, waiting for SSH to be available…
Detecting operating system of created instance…
Provisioning created instance…
Copying certs to the local machine directory…
Copying certs to the remote machine…
Setting Docker configuration on the remote daemon…
To see how to connect Docker to this machine, run: docker-machine env aws01

AWS 上に作成した docker-engine を操作してみる

  • docker-machine ls

で作成したインスタンスの情報を取得できます
IP アドレスを取得するときに API をコールしているので API のコールに失敗するとエラーが表示されます

docker コマンドで AWS 上のインスタンスを操作できる準備をします

  • eval “$(docker-machine env aws01)”

を実行すると以降の docker コマンドの操作が AWS 上のインスタンスに対する操作になります
docker-machine env aws01 だけ実行するとわかりますが、いくつかの環境変数を export するコマンドが出力されます
それを eval しているだけです

例えば すでに CentOS 上で docker-engine が動作しておりイメージが存在しているのであれば eval したあとで docker images とかを実行してみると何もイメージが表示されないことが確認できると思います

  • docker run busybox echo hello world

超簡単なコンテナの実行です
DockerHub から busybox イメージをダウンロードして echo しているだけです
問題なく実行できると思います
終了後に docker ps -a で確認すると終了していることがわかると思います

これだと本当に AWS 上のインスタンスで実行されているかわからないので nginx を動かしてアクセスできるか確認してみます

  • docker run -d -p 8000:80 nginx

nginx のイメージを取得して起動します
インスタンスの 8000 番をコンテナの 80 番にフォーワードして -d でデタッチモードで起動します

  • curl $(docker-machine ip aws01):8000

で Nginx の「Welcome to Nginx」の HTML が返ってくることを確認してください
返ってこない場合は 8000 番がセキュリティグループで閉じられていると思います
docker-machine で作成したインスタンスには docker-machine というセキュリティグループが自動で作成され割り当てられるようで、これが 22 と 2376 しかデフォルトでは空いていませんでした
ポートを開放して再度実行して HTML が返ってくることを確認してください

  • docker-machine aws01 stop

使わなくなったら上記コマンドでインスタンスを停止することができます
再度使いたい場合は start で OK です

最後に

今回は AWS 上で試しましたが docker-machine には driver という概念があり、様々なクラウドサービスや仮想環境にアクセスできる driver が備わっています
https://docs.docker.com/machine/drivers/

今後もいろいろな driver が追加されると思います
どうしても追加してほしい driver があれば pull - req でもいいと思います

テストとかで試すために自分の環境に docker-engine をインストールすることはあると思いますが、プロダクションなどで複数のインスタンスに docker-engine をインストールする場合は docker-machine を使って構築すれば管理が楽になると思います

あとは、SWARM という欄が ls 実行時にあったので docker-swarm と連携して、複数クラウドサービス間で docker クラスタを組めたりする感じでしょうか
この辺りはまた次回にでも試せればと思います

トラブルシューティング

  • Error creating machine: Error with pre-create check: unable to find a subnet in the zone: us-east-1a
    デフォルトのリージョンは us-east-1a にインスタンスを作るみたいで、そこに VPC とサブネットがないと上記のエラーになります

  • Error creating machine: Error in driver during machine creation: Error launching instance: Problem with AWS API call: Non-200 API response: code=400 message=Your requested instance type (t2.micro) is not supported in your requested Availability Zone (ap-northeast-1a). Please retry your request by not specifying an Availability Zone or choosing ap-northeast-1b, ap-northeast-1c.
    デフォルトのインスタンスタイプは t2.micro のようでゾーンによっては t2.micro が推奨されないタイプになります
    その場合に上記のエラーが発生します
    インスタンスを作成するゾーンを変更するかインスタンスタイプを変更 (--amazonec2-instance-type) してあげれば OK です

  • Host already exists: “aws01”
    すでにインスタンスが作成されているため create コマンドが失敗しています
    実際に AWS 上にインスタンスは作成されていないみたいで、不整合が発生しているのでローカルの情報を削除してあげましょう

    docker-machine rm -f aws01

  • Error creating machine: Error with pre-create check: There is already a keypair with the name aws01. Please either remove that keypair or use a different machine name.
    キーペアがすでに作成されているためにエラーが発生しています
    マネージメントコンソールから EC2 -> キーペア -> aws01 を削除して再度実行してください

  • Error creating machine: Error with pre-create check: unable to find a subnet in the zone: ap-northeast-1c
    指定したゾーンにサブネットが存在しない場合に発生するエラーです
    マネージメントコンソールの VPC から該当のゾーンにサブネットを作成しましょう

参考サイト

2015年11月11日水曜日

nRF51822 の Advertise で Scan レスポンスパケットを設定する方法

概要

Scan レスポンスパケットは Advertising でスキャンした後にレスポンスとして Peripheral が Central に送信できるおまけパケットです
Advertising パケットは上限が 31 バイトになっておりその上限を超えて何かデータを送りたいときなどに使います
データの構造は Advertising パケットと同様になっています

環境

  • Windows7 64bit
  • nRF51822
  • nRF51 DK
  • nRF51 SDK 9.0.0

背景 (なぜ Scanパケットを設定したかったのか)

ちょっと長くなりますが背景を詳しく説明します

サンプルのせいにするわけではないですが nRF51 SDK に含まれている各種サンプルでは基本的に Scanパケットを設定していません
で、Central 側の実装を Python ベースの gattlib というライブラリを使って実装していたのですが、Advertising パケットをスキャンした際に DeviceName が含まれずに困っていました
具体的には DiscoveryService.discover というメソッドを使うと DeviceName が取得できない状況でした

切り分けのため TI 社が提供する SensorTag や iPhone で実装した Peripheral デバイスを使ってスキャンしてみたのですが、その場合はうまく DeviceName が取得できました
なので、nRF51822 上に実装しているアプリが悪そうだ、ということでいろいろ調べたところ Scan レスポンスパケットを設定してあげることで、nRF51822 でも DeviceName を取得することができるようになりました

更に余談になってしまうのですが、hcitool の lescan というコマンドを使っていると DeviceName が (unknown) と表示されることにも気付きました
unknown がちょくちょく出ていましたが、ちゃんと DeviceName が表示されることもあったので初めは無視していました
また、nRF51822 の場合でも SensorTag の場合でも unknown が表示されていたので「こんなもんなのか」と思っていたのですが、discover すると nRF51822 だけ DeviceName が取得できないのはおかしいということに気が付き詳しく調べてみたところ Scan レスポンスパケットという仕様にたどり着き設定してみたらうまくいった感じです

ということは今思うと SensorTag は Scan レスポンスパケットを設定しておりかつ、gattlib の DiscoveryService.discover は Advertising パケットの DeviceName を見ているわけではなく、Scan レスポンスパケットの DeviceNam を見ている ( のではと今になって予想しています、コードレベルまで見ていないので正解がどうかは不明です )

長くなりましたが、まとめると

  • gattlib の DiscoveryService.discover で DeviceName が取得したかった

ために設定した感じです

設定方法

前置きかなり長くなりましたが実装方法です
nRF51 SDK のサンプルでは多くの場合、 main メソッド内で各処理の初期化を行っています
その中の advertising_init というメソッドで Advertising パケットの設定をしておりそこを修正します

具体的なコードの全貌は以下のとおり

static void advertising_init(void) {
    uint32_t      err_code;
    ble_advdata_t advdata;
    ble_advdata_t rspdata;

    // Build advertising data struct to pass into @ref ble_advertising_init.
    memset(&advdata, 0, sizeof(advdata));
    memset(&rspdata, 0, sizeof(rspdata));

    advdata.name_type               = BLE_ADVDATA_FULL_NAME;
    advdata.include_appearance      = true;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    advdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    advdata.uuids_complete.p_uuids  = m_adv_uuids;

    rspdata.name_type               = BLE_ADVDATA_FULL_NAME;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    err_code = ble_advertising_init(&advdata, &rspdata, &options, on_adv_evt, NULL);
    APP_ERROR_CHECK(err_code);
}

ポイントは rspdata 変数を宣言して rspdata.name_type = BLE_ADVDATA_FULL_NAME; として DeviceName を設定するところです

冒頭でも説明していますが、Advertising パケットと Scan レスポンスパケットはデータ構造が同じです
なので、すでに宣言している advdata を使いたいところですがこれを使うとうまく動作しませんでした

また、 rspdata に DeviceName だけ表示するように設定していますが、これもポイントで他のパラメータも advdata と同じように設定したところ、これまたうまく動作しませんでした
( うまく動作しない明確な理由がわからない状態です、すいません )

なのでとりあえず上記のように、記載することで Scan レスポンスパケットを設定することができました

最後に

これで再度 hcitool lescan をすると nRF51822 で実装した BLE デバイスに unknown が表示されなくなっていることが確認できると思います
Advertising + Scan レスポンスパケット両方で DeviceName を設定するようにしたためです

ただ、他のデバイスを見るとほとんどのデバイスで unknown になることがあるので、おすすめ実装的にはやっぱりどっちかだけで DeviceName を送信するほうがいいのかもしれません
まぁ確かに同じデータを 2 回送るのであれば無駄といえば無駄ですからね
とは言え Central 側の実装も考慮しなければいけないと考えるととりあえず両方設定しておいたほうが無難といえば無難なのでしょうか

相変わらず BLE の実装は大変です

2015年11月10日火曜日

スタンドアロンになってしまった RaspberryPi を救う方法

概要

想定ケースとしては

  • RaspberryPi (RPi) は常に無線 LAN に接続しており他のマシンから SSH でログインして操作していた

という状況で突如無線 LAN が停止してログインできなくなったときに救うケースを考えてみます

環境

  • Raspberry Pi Type B Single Board Computer 512MB
  • Raspbian 8.0 (Jessie)

最終手段

いきなりですが、最終手段です
どうしても RPi を操作できる状況にできない場合は、ネットワークが復旧したあとで電源プラグを引っこ抜いて再起動するしかないと思います

電源を直接抜いてシャットダウンすると SD カードが破損して最悪 OS 起動しなくなったりデータが正確に保存されないなどのリスクがあります
自分は一度だけ OS が起動しなくなり SD カードをフォーマットしなおしたことがありますが、ほぼ遭遇したことはありません
なので「どうしても」というケースでは実施して問題ないと思います

今回は上記を最終手段として、それまでに救えるいくつかの方法を検討していきます

キーボードおよびディスプレイを使って救出する

おそらくこれが一番メジャーだと思います
キーボードしかない場合も想定して復旧方法を検討します

有線のキーボードおよびディスプレイに接続できる状況

この状況があれば一番救える確率が高いです
キーボードは USB でディスプレイは HDMI とします

この場合はまずそのままキーボードとディスプレイを RPi に接続します
で、キーボードから何かしらの信号を送れば HDMI 側にも信号が飛んで RPi の CUI が表示されればそのまま救出完了になります
( RPi の OS のバージョンが Raspbian Jessie だった場合はデフォルトが GUI なのでキーボードの Tab や Alt 十字キーを使ってターミナルを起動するなりして救う必要があります、キーボードが HHK で十字キーボードがないとか OS のキーボード配列の設定が違うとか問題に遭遇する可能性もありますが、そうなったら頑張るしかないです )

ですが、キーボードとディスプレイを接続して信号を送ってみたのにディスプレイ側に信号が来ないなんてことに遭遇すると思います
RPi は起動時にディスプレイが繋がっていれば HDMI の出力を ON にするのですが、HDMI が繋がっていない状態で電源を ON にすると HDMI の出力が行われません
( 自分の場合は上記に該当しており、デフォルト GUI だった OS を CUI の状態にしたら発生しました )
その場合は画面が見えない状態でキーボードで HDMI の出力を ON にするコマンドを実行しなければいけません
コマンドは以下の通り

  • tvservice -o
  • tvservice -p
  • fbset -depth 8
  • fbset -depth 24

簡単に説明するとディスプレイの出力を OFF -> ON して画面のフレームバッファの設定を 8 -> 24 に変更します
このコマンドを画面が真っ暗な状態で、想像しながらタイプする必要があります
間違ってタイプした場合は Ctrl + e -> Ctrl + u を駆使して一旦クリアした後で再度冷静にタイプしましょう
タイプが成功して HDMI への信号が ON になれば画面が表示されて救出完了になります

あとコマンドをいきなり実行できる状態で RPi が起動していればいいのですが、pi ユーザで一度ログインしないとダメな場合には、そこから想像しながらタイプする必要があるので若干難易度があがります

どうしても画面が表示されない場合は「sudo reboot」もありです
もしくは「sudo shutdown -h now」でシャットダウンして RPi 上の LED が点滅しないことを確認して電源を抜いてから再起動でも問題ないと思います

有線のキーボードはあるがディスプレイがない

これは、上記の通り想像しながらキーをタイプするしかないと思います
ディスプレイがないのでディスプレイに出力してもしょうがないのでこの場合は

  • sudo reboot

or

  • sudo shutdown -h now

のどちらかを頑張ってタイプして復旧させるしかないと思います
そしてこの場合は GUI だと終わります
さすがに GUI をキーボード Only で想像しながらタイプするのは無理があります

頑張ってタイプしても再起動しない、停止しないという場合は、キーボード自体の信号が送信されていない可能性があるかもしれません
例えば PPi を電池やモバイルバッテリーで動かしている場合に充電が足りずに RPi に十分な電圧が加わらず USB ポートが正常に動作しないなどが考えられます
RPi は通常 5V (家庭用の電源コンセント) の電圧がかかることを想定しています

ディスプレイはあるが無線キーボードしかない

例えば Bluetooth の無線キーボードだけある状況です
基本は有線と変わらず接続できたら停止 or 再起動コマンドを想像しながら発行するということしかできません

ただ、無線の場合は有線に比べて信号を送るまでの難易度があがります
まず RPi に Bluetooth レシーバが接続されていることが絶対条件です
これがないと終了です
レシーバがあり無線キーボードがアドバタイズできる状況であるならば救える可能性はあります

が、ここからもハードルが高いです
RPi の場合 Bluetooth キーボードと接続するにはキーボードとペアリングするためのコマンドを発行する必要があります
(参考 : RaspberryPi で Bluetooth キーボードを接続する方法, RaspberryPi の bluetoothctl で無線キーボードに接続 )
一度接続したキーボードは trust することで自動で再接続してくれるオプションがあるのですが、このオプションは RPi が起動する場合でないと使えません
なので RPi が起動中に Bluetooth キーボードが接続できる範囲にきても自動で接続してくれません

ではどうするか、ですが自分は以下のスクリプトを cron で回す設定を事前に行っています
bluetoothctl と expect で無線キーボードに定期的に接続するスクリプト

ここまで事前に用意できていて初めて無線キーボードが使えるようになります
有線に比べてかなりハードルが高いので、緊急時はできれば有線キーボードを準備してください

キーボードもディスプレイもない

これは相当厳しいです
最悪 PC があればなんとかなります (後述) が、この場合は電源 OFF をするしかないです

PC を持っている場合

一台 PC を持っている場合に救出できる方法がないか検討します

シリアル通信を使う

RPi の GPIO を使って RPi 上の信号を PC に表示します
ただ、シリアル通信をするのであれば PC 以外に以下の機材が必要になります

  • ジャンパケーブル
  • FTDI シリアル - USB 変換アダプタ
  • USB ケーブル

やり方や機材等の詳細はこちらを御覧ください
シリアル通信に必要な機材はこれ以外のパターンもたくさんあります
が、とりあえず PC だけでは無理です

そしてシリアル通信の最大の弱点は PC を停止した状態で配線しないと信号が受け取れない点にあります
・・・そう、起動している RPi だと結局シリアル信号をモニタすることができないのです

じゃあなんで紹介したんだとなりますが、1 つは電源での OFF -> ON 後にとりあえず正常に動作しているか確認したい場合に使えます
もう 1 つは実は起動した状態でシリアル通信できる方法があると誰かが教えてくれのではと、思ったからです
もしかしたら、あとで調べたらあっさり方法がわかるかもしれませんが

最後に

RPi は一度セットアップしてサーバとして動かすのであれば電源だけあれば OK で省スペースなのですが、いざぶっ壊れて対応しなければいけないときに、とたんにハードルと必要になる機材が増えるようなイメージがあります

しょうがないと言えば確かにしょうがないですが、もっとスマートな方法はないものだろうかと思いました