obnizとスマホでテレプレゼンスロボットを作ってみた

最近ハマっているobnizとメルカリで4000円で買ったiPhone5Sを使って、テレプレゼンスロボットを作りました。
テレプレゼンスロボットとはテレビ会議+ロボット+遠隔操作技術を組み合わせたロボットのことです。(Wikipediaより)

まずは動画をご覧ください。実際にテレビ会議している様子ではありませんが、コンセプトは伝わるかと思います。


obnizでテレプレゼンスロボットを作ってみた

この記事では、「こうせん」と名付けたこのテレプレゼンスロボットについて以下の順で述べます。

  1. 製作のきっかけ
  2. 仕組み
  3. こだわり・アピールポイント
  4. 材料と費用
  5. 今後やりたいこと

なお、こうせんの操作画面のコードはこちらです。

1. 製作のきっかけ

昨年の夏に会社でテレプレゼンスロボットが利用されているのを見て、同じような物を自分でも作りたいと思ったことがきっかけです。
テレビ電話の機能自体はスマホのアプリで十分なので、あとは通話先のスマホを動かすことができれいいと思いましたが、その時は方法が思いつきませんでした。
そして昨年の秋にobnizと出会って、これを使えば作りたかった物が作れると確信しました。
ネット上にはobnizを使ったロボットの製作例が豊富に載っていて、それらがとても参考になりました。感謝です。

それで冬休みに製作して、年明け同僚に見せてフィードバックもらって、改修して、今日のこの記事に至ります。

2. 仕組み

仕組みは単純で、テレビ電話の機能は「FaceTime」というアプリに任せて、ロボット操作の機能をobnizで実装しています。
動画ではMac上からロボット操作とテレビ電話を行なっていましたが、ロボット操作とテレビ電話の機能は連携していないので、下記右図の通り、操作側の端末は別々でも問題ありません。

f:id:sonomirai:20190120004725p:plain
仕組み

なお「FaceTime」はデフォルトだと着信時に下記の表示が出て、スワイプしないと通話が始まらないのですが、iOS11以降だと自動応答の設定ができるので、これを活用すると見守りロボット的な役割を果たすこともできます。自動応答の設定はこちらに記事がありました。

3. こだわり・アピールポイント

机上で利用できるようにする

多くのテレプレゼンスロボットは床の上を移動しますが、こうせんは大きさ的に机上での利用を前提にしようと決めていました。ただ机上で利用するためには、こうせんが落ちたり、机上のものを落とさないようにする必要があるので、そこは先端に距離センサーを取り付けて、センサーの値が閾値以上になった時には緊急停止するようにしました。
また机上という狭いスペースで動く際には、如何に小回りがきくかも重要なので、綺麗に旋回できるシャーシを結構探しました。当初は見た目のかわいいタミヤカムプログラムロボットを使っていたのですが、どうも綺麗に旋回ができなかったので、別のシャーシを探すことにして、こちらの記事を参考に、今のシャーシを購入しました。

操作者の意図を受け側に伝える

操作者の意図が受け側に伝わった方がいいと思ったので、obnizをこうせんの先端につけて、ボタンクリック時には「前進」などの文字が表示され、緊急停止時には「緊急停止」の文字が点滅して表示されるようにしました。

f:id:sonomirai:20190120225525p:plain:w300
操作時のディスプレイ表示

フールプルーフを意識してUI設計する

緊急停止時にはいったん後退してもらい、それから旋回してもらいたいので、緊急停止時には後退以外のボタンを非活性化して、そもそも押せないようにしました。

f:id:sonomirai:20190120231618p:plain
通常の操作画面と緊急停止時の操作画面

壊れにくい設計にする

製作中、センサーがうまく働かずにこうせんが机から落ちて、シャーシが真っ二つに割れてしまったことがあり、そこから壊れにくい設計を真剣に考えるようになりました。
壊れにくい設計の1つとして、以前はモバイルバッテリーを横向きに配置していたのですが、これがシャーシが真っ二つに割れた一因と考え、モバイルバッテリーを縦向きに配置するようにしました。一応わざと机から落としたところ、今度はシャーシは割れませんでした。怖くて1回しか落としていませんが。。。

f:id:sonomirai:20190120234046p:plain
モバイルバッテリー配置の変更点

壊れにくい設計についてはまだまだ不十分だと思っているので、今後も改善していきたいです。なお現状のこうせんの各面は以下のようになっています。
シャーシが白いのは補強用のテープを貼ったからですが、このテープでどれだけ壊れにくくなったのかは正直よくわかりません。意味ない気もしてます笑

f:id:sonomirai:20190120232057p:plain
こうせんの各面

4. 材料と費用

材料と大まかな費用は以下の通りです。

材料 費用(円) 備考
obniz 6000 たまにキャンペーンで5000円くらいになっています
iPhone5S 4000 メルカリで中古を購入。スマホなら何でもいいです
シャーシ 1400 Amazonで購入可
モバイルバッテリー 1500 筆者はポケモンGoにハマってた時に購入してその後使ってなかったやつを使いました
距離センサー 450 秋月電車で購入可
センサー取付用金具 100 ホームセンターで購入可
本立て 100 家にあったやつを使いました
その他の部品 1000 ネジ, スペーサー, 導線など。ザクっと1000円あれば足りたはずです
合計 14550

本体の費用、スマホ含めて15000円以内というのは、個人的には結構安くできたと思っています。ただ試行錯誤の段階でカムプログラムロボットをはじめとしたシャーシやタイヤを買っていてそこは別途お金が掛かっているので、今回の製作を通しては、IoTロボット製作はソフトウェア開発に比べるとお金がかかるなぁという感想を持ちました。

5. 今後やりたいこと

  • 実際にテレビ会議などでこうせんを使って、フィードバックをもらいたいと思っています。
  • 今は赤外線方式の距離センサーを使っているので、超音波方式の距離センサーも試してみたいです。
  • 物体認識もやってみたいです。

Google Homeとobnizで部屋の電気をON/OFFする

2019年2月16日 追記 package.jsonを追記しました。

2018年12月29日 追記
obniz単体だとエンドポイントを作れないと思っていたので、RunKitでエンドポイントを作っていたのですが、実際はobniz単体でも、Event機能でWebhookを選択すればエンドポイントを作れました。失礼しました。訂正します。


この記事はobniz Advent Calendar 2018 10日目の記事です。

obnizとサーボモータで部屋の電気をON/OFFできるようにして、Google Homeから音声操作できるようにしました。 まずは動画をご覧ください。スマート電球を使わず物理的にスイッチを叩いて電気をON/OFFしている様子がお分りいただけると思います。(カメラのライトをつけているので明るさはあまり変わってませんが。)


Google Homeとobnizで部屋の電気をON/OFFする

私はこれまで電子工作もVUIアプリ開発もほとんど経験がなかったのですが、obnizのハンズオンに参加して「こりゃ簡単すぎて自分でも何か作らなきゃ嘘だぜ」と思ってこのアプリを作りました。
電子工作、VUIアプリ開発の双方でたくさん学びがあったので、この記事では実施した内容とそこで得たノウハウを順を追って書いていきます。この記事が、私のような初心者の方の参考になれば幸いです。
記事は「1. 準備編」「2. 実装編」「3. 運用編」の3部構成です。

1. 準備編

今回使ったものはGoogle Home mini, obniz, サーボモータ, スイッチカバーです。

1-1. obnizとサーボモータの接続

アプリ開発の第一歩。張り切ってobnizとサーボモータを接続しよう!としたところ、接続部分が受け口と受け口で付けられませんでした。

f:id:sonomirai:20181201221004j:plain:w300
受け口と受け口!

変換用の部品が存在することは知っていたのですが、名前が分からずネットで注文できなかったので、サーボモータ側のコードを切ってねじってobnizに差し込んで動かしました。その後、変換用部品は「ピンヘッダ」という名前であると知りました。

f:id:sonomirai:20181208104645p:plain
ピンヘッダ

1-2. スイッチカバーへのサーボモータの取り付け

元々のスイッチカバーは丸みを帯びていてサーボモータを固定しづらそう&部屋のもの傷つけるのが嫌だったので、サーボモータの取り付け方で迷いました。
ヒントを求めて向かったホームセンターで、サーボモータを固定しやすそうな角ばったスイッチカバーが80円で売っていたので、それを買ってサーボモータをアロンアルファで固定して、元のスイッチカバーと付け替えました。特に問題なく付け替えられて一安心。

f:id:sonomirai:20181208104708j:plain:w400
左が元々のスイッチカバーで右が今回のスイッチカバー

これで準備は終わりです。

2. 実装編

実装は以下のように段階的に進めました。【】内はざっくりとした作業カテゴリです。
1.【IoT化】obnizのWeb画面からサーボモータを動かす
2.【API化】RunKitからサーボモータを動かす
3.【VUI化】音声でRunKitを叩いてサーボモータを動かす
4.【UI改善】Google Home周りのユーザビリティを上げる

実装の流れをざっくりと概念図で示すと下記の通りです。

f:id:sonomirai:20181201223245p:plain
実装の流れ

2-1.【IoT化】obnizのWeb画面からサーボモータを動かす

ハンズオンやパーツライブラリのサンプルコードを参考に、obnizのWeb画面でこちらのプログラムを作成しました。実行画面は以下の通りで、ボタンをタップすると電気が点灯/消灯します。

f:id:sonomirai:20181201225140p:plain:w200
実行画面

2-2.【API化】RunKitからサーボモータを動かす

次にこれをGoogle Homeから実行しようとしたところで、obnizのクラウドシステムには外部からプログラムを叩くためのエンドポイントを作成する機能がないので、別のサービスを使う必要があることに気付きました。
[訂正]上記は間違いで、obnizのEvent機能でWebhookを選択すればエンドポイントを作成することができました。
AWSのLambdaなどのサービスを使う必要がある?と思いつつググっていたところ、RunKitというサービスでエンドポイントを作っている資料があったので、とりあえずRunKitを使ってみることにしました。
RunKitはとても便利で、作成したコードを叩くためのエンドポイントを勝手に作成してくれます。
点灯用のコードと消灯用のコードを分けて作成しました。点灯用のコードはこちらです。

curlでRunKitのエンドポイントを叩くと、電気が点灯/消灯されるところまで確認しました。

2-3.【VUI化】音声でRunKitを叩いてサーボモータを動かす

点灯/消灯のエンドポイントを準備できたので、次は音声操作でエンドポイントを叩けるようGoogle Homeの設定をします。
Google Homeでのアプリ開発ではGoogleアシスタント, Action on Google, Dialogflowといった複数のツールが出てきますが、詳細は割愛します。WEB+DB PRESS Vol.105のスマートスピーカの記事が分かりやすかったです。

今回は以下の通り設定しました。

  1. Action on Googleのプロジェクト名を設定する
  2. DialogflowのIntentsでFulfillmentを有効化する
  3. IntentsとFulfillmentの関数を紐付ける

2-3-1. Action on Googleのプロジェクト名を設定する

VUIアプリ開発にあたり、まず最初にAction on Googleのプロジェクトを以下のどちらで作るか迷いました。

  1. 点灯用と消灯用でプロジェクトを分けてそれぞれのDefault Welcome Intentで処理をする
  2. プロジェクトは1つにして、Intentによって点灯と消灯の処理を分ける

前者だとプロジェクト名のみの発話でいいので、「OK Google, 電気つけて」「OK Google, 電気消して」という短い発話で操作ができます。
ただ発話毎にプロジェクトを分けるのは流石にイケてなさそうと言う気がしました。
いっぽう後者は、例えばプロジェクト名を「ルームライト」とした場合、「OK Google, ルームライト」と発話して、Google Homeの応答を待ってから、「電気消して」と発話することになるので、かなり使いづらそうだと悩みました。
そこでヒントを求めて読んだWEB+DB PRESS Vol.105Google Homeの記事に「プロジェクト名を使ってフレーズ名」という発話ができると書いてあって、コレだ!と思いました。これなら少し発話は長くなりますが、「OK Google, ルームライトを使って電気つけて」と1フレーズで操作ができるのでユーザビリティとしては許容範囲だと判断だし、後者で実装することにしました。

2-3-2. DialogflowのIntentsでFulfillmentを有効化する

Action on GoogleのActionsからDialogflowの編集画面を開いて、インテントを設定します。点灯用のインテントの設定は以下の通りです。点灯用インテントから先ほど設定したRunKitの点灯用コードを実行するため、最後の「Enable webhook call for this intent」を有効にします。

f:id:sonomirai:20181202235940p:plain:w400
点灯用インテントの設定

2-3-3. IntentsとFulfillmentの関数を紐付ける

DialogflowのFulfillmentに移動して、RunKitのエンドポイントを実行するための設定をしようとしたのですが、ここでつまづきました。感覚的に、Intentsとそれに対応するFulfillmentを1:1で紐付けするためのGUIがあるはずだと思ったのですが、Intentsの設定画面にもFulfillmentの設定画面にもそのようなGUIがなかったのです。
そこで有識者の方に伺って、IntentsとFulfillmentを紐付けするGUIはないので、そこは自分でIntentsとFulfillmentを紐付けするコードと振分けするコードを書く必要がある、と教えていただきました。(よく見るとInline Editorのサンプルコードにこの辺りのことが書いてありましたが、気づきませんでした。。。)

f:id:sonomirai:20181207215445p:plain:w500
インテントとフルフィルメントのマッピング

それではIntentsとFulfillmentを紐付けます。Fulfillmentから外部サービスを呼び出すこともできますが、今回は素直にInline Editorに紐付けのコードを書くことにしました。index.jsはこちら, package.jsonこちらです。(Inline Editorの裏ではCloud Functions for Firebaseが動くので、いくらか課金されます。)
ここまで設定してActions on GoogleのSimulatorを実行すると、「ルームライトを使って電気つけて」で電気をつけられるようになり、実運用できるようになります。

f:id:sonomirai:20181207224547p:plain:w300
Simulator実行画面

2-4.【UI改善】Google Home周りのユーザビリティを上げる

2-3までの実装で、音声で電気のON/OFFが可能になったので、実際に使い始めたのですが、使って見ると以下のような問題が発生しました。(Google Homeが認識した発話の内容はGoogle Homeアプリのマイアクティビティから確認します。)

  1. 「ルームライトを使って消灯」が「ショート」に誤認識される
  2. ルームライトを使って消灯」が「ウムラウト」に誤認識される
  3. 「ルームライトを使って消灯」をついつい「ルームライト消灯」と言ってしまう

問題3は実装の初期段階から懸念していて、気をつければ大丈夫だろうと思っていたのですが、たまに間違った発話をしてしまうことがありました。
結論から言うと問題1はDialogflowのEntitiesでフレーズ名にエイリアスをつけることで解決できました。また問題2, 3はGoogle Homeアプリのルーティンで発話にエイリアスをつけることで解決できました。それぞれ述べます。

2-4-1. DialogflowのEntitiesでフレーズ名にエイリアスをつける

問題1を解決するため、ルームライト内で「ショート」と認識したら「消灯」と捉えるようにエイリアスをつけます。この設定はDialogflowのEntitiesから以下のように設定します。

f:id:sonomirai:20181208114041p:plain:w400
Entitiesの設定

Simulatorで期待する動作をすることを確認します。

f:id:sonomirai:20181208114200p:plain:w300
Simulator実行画面

2-4-2. Google Homeアプリのルーティンで発話にエイリアスをつける

次に「ルームライト」が「ウムラウト」と誤認識される問題と「ルームライト」と言ってしまい、期待する動作を得られない問題に対処します。前述の通り、今回は「プロジェクト名を使ってフレーズ名」という発話をする想定でいます。問題1はフレーズ名の誤認識に対する対処で、これはDialogflowの設定で解決できたのですが、問題2, 3はDialogflowの世界の外の話なので、Dialogflowの設定では解決できません。
そこで有識者の方に伺ったところ、Google Homeアプリのルーティンでエイリアスを設定できると教えてもらいました。(ルーティンはエイリアスをつけるだけではなく色々なことに使える機能なのですが、説明は割愛します。)この機能はとても強力で「ルームライトを使って電気つけて」に対して「電気つけて」というエイリアスをつけることができるので、「ルームライトを使って」の発話がいらなくなり、問題2, 3が発生しないようにできるのです!(これに気付いた時はめちゃめちゃうれしかった。)

ルーティンの設定は「Google Homeアプリ>GOOGLEアシスタント>その他の設定>アシスタント>ルーティン」から行います。

f:id:sonomirai:20181208160152p:plain:w500
ルーティンの設定

これでプロジェクトの保守性を保ちながら、短い発話で操作することが可能になりました!

3. 運用編

今回作成したアプリを2週間くらい使っての感想です。

  • 就寝時に電気を消しにスイッチまで歩く必要がなくなり、便利になった。(特に最近寒いので、ベッドから出ないで済むのが有難い。)
  • 発話からサーボモータが動くまでのタイムラグ、およびGoogle Homeがしゃべり出すまでのタイムラグが気になるといえば気になりなるので、そこは今後改善したいです。
  • この記事で紹介した内容に加えて、obnizのイベント機能を使って毎朝自動で電気をつける設定もし他のですが、これが思った以上にいい感じでした。今までは目覚まし時計に叩き起こされていたので朝起きるのが辛かったのですが、今は自動で電気がついた5〜10分後に目がパッと覚める感覚になり、気持ちよく起きれるようになりました。

f:id:sonomirai:20181208161915p:plain:w300
イベント設定(時刻指定はUTC

画像の中で設定しているprivate_LightOn.htmlはこちらです。

まとめ

obnizとサーボモータで部屋の電気をON/OFFできるようにして、Google Homeから音声操作できるようにしました。またobnizのイベント機能を使って毎朝自動で電気をつける設定もしました。両方とも2週間くらいいい感じで使えているので、今後も継続して使おうと思っています。

SORACOM IoTもくもく会参加レポート

SORACOM主催のIoTもくもく会に参加してきた。
手順はGitHubにあるので詳細省略するが、ざっくり言うと「Raspberry Piに接続したセンサデータ(*)をインターネット上にSoracom Airで送って、送ったデータをSoracom Beamでセキュアに連携したり、Soracom Harvestで可視化する」というハンズオンだった。
Rasberry Piと超音波センサーとSORACOM Airがあれば、家で一人ででもできそう。

けっこう時間がカツカツでHarvestでの可視化まではたどり着けなかったのが悔しいが、IoTの手触りとSORACOMがIoTやる時に担う役割について体験できてよかった。

書き残しておきたいが2つあるので、記しておく。

1つめ。SORACOMのサービスの中心にはAirがいて、Airを使ってIoT始めようとした時にユーザが困るところを他のサービスでケアする構成っぽいと理解した。 AirはモバイルWifi用途でずっと使っていたが、Beamはじめとしたその他のサービスはいまいち用途がわからなくて真面目に追っていなかった。 今日のハンズオンで、Raspberry Piなどのデバイス側でセキュアな通信を実装するのは手間なので、Beamでクラウド側に処理をオフロードすると言う話を聞き、そこで初めてBeamはじめとしたSORACOMのサービス群の用途のイメージが湧いた。

2つめは、IoT始めるにはモノ(センサ)、インターネット、クラウドの3つが必要という話。
IoTに必要なのはモノ、インターネット、サーバだと思っていたので、白目を向いてしまった(笑) なんというか、時代の波に乗れるのは、その前の波にちゃんと乗れていた人だなぁと思った。
基盤自動化の波に乗れるのは、それ以前にソフト開発のプラクティスを実践していた人だけ。
IoTの波を乗れこなせるのは、それ以前にクラウド上でシステム開発していた人だけ。
そこを意識せず流行りに飛びついても溺れるだけなので、そこは要注意だと思った。

深夜のノリでポエムになってしまったが、IoTもくもく会は勉強になった。参加してよかった。
別のテーマの時にまた参加したい。

サクッとBCryptで文字列を暗号化する

コマンドライン引数で文字列をBCryptで暗号化するコード書いたのでメモしておく。
Mavenなどのパッケージ管理ツール使わず、とにかくサクッとやる想定。

パッケージダウンロード

利用するライブラリをサイトから手動ダウンロードする。

spring-security-coreだけだと実行時にcommons-loggingがないというjava.lang.NoClassDefFoundErrorが出力されるので、とりあえず追加。

実装

これらのライブラリをlib配下に格納し、以下の2クラスを実装する。
引数をBCryptで暗号化するCLBCryptPasswordEncoder.java

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.apache.commons.logging.LogFactory;

public class CLBCryptPasswordEncoder{
  public static void main(String args[]){

    String textPassword = args[0];

    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    System.out.println(encoder.encode(textPassword));
  }
}

平文パスワードと暗号化済パスワードが一致するか確認するCLBCryptPasswordMatcher.java

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.apache.commons.logging.LogFactory;

public class CLBCryptPasswordMatcher{
  public static void main(String args[]){
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    String textPassword = args[0];
    String encryptedPassword = args[1];

    if (encoder.matches(textPassword, encryptedPassword)) {
      System.out.println("matched");
    } else {
      System.out.println("mismatched");
    }
  }
}

ディレクトリ構成

ディレクトリ構造は以下のとおり。

  • CLBCryptPasswordEncoder.java
  • CLBCryptPasswordMatcher.java
  • lib/
    • commons-logging-1.2.jar
    • spring-security-core-5.0.2.RELEASE.jar

コンパイル

$ javac -cp "lib/*" CLBCryptPasswordEncoder.java
$ javac -cp "lib/*" CLBCryptPasswordMatcher.java

カレントディレクトリにクラスが生成される。

実行

暗号化と検証の実行。classpathにカレントディレクトリを追加。

$ java -cp "lib/*:." CLBCryptPasswordEncoder sonomirai
$2a$10$ALcXPgrpOQKXoIyrgS90huCbgR906LtWrH1dOsZmHtBZdSB19n9Bi
$ java -cp "lib/*:." CLBCryptPasswordMatcher sonomirai '$2a$10$ALcXPgrpOQKXoIyrgS90huCbgR906LtWrH1dOsZmHtBZdSB19n9Bi'
matched
$ java -cp "lib/*:." CLBCryptPasswordMatcher dummypswd '$2a$10$ALcXPgrpOQKXoIyrgS90huCbgR906LtWrH1dOsZmHtBZdSB19n9Bi'
mismatched

こんな感じでやりたかったことはやれた。

Java本格入門13章をdocker上で写経する

はじめに

Java本格入門の13章「周辺ツールで品質を上げる」を写経した。
Maven使った操作はdocker上で写経したので、その時の手順を記録しておく。
誰かの役に立てば幸い。なお写経したコードはこのリポジトリにある。

準備

HTML形式のレポートを見るため、centos/httpdイメージからコンテナを起動する。

コンテナ起動

$ docker run --name javabook -h javabook -d -p 8080:80 centos/httpd

http状態確認

ブラウザでhttp://localhost:8080にアクセスして「It works!」と表示されることを確認する。

コンテナ内に入る

$ docker exec -it javabook /bin/bash

パッケージインストール

[root@javabook /]# yum install -y vim git java maven

Javamavenバージョン確認

[root@javabook /]# java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
[root@javabook /]# mvn -version
Apache Maven 3.0.5 (Red Hat 3.0.5-17)
Maven home: /usr/share/maven
Java version: 1.8.0_151, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.el7_4.x86_64/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "4.9.49-moby", arch: "amd64", family: "unix"

Git準備

[root@javabook /]# git config --global user.email <メールアドレス>
[root@javabook /]# git config --global user.name <ユーザ名>
[root@javabook /]# git clone https://github.com/acroquest/javabook-maven-example.git
Cloning into 'javabook-maven-example'...
remote: Counting objects: 42, done.
remote: Total 42 (delta 0), reused 0 (delta 0), pack-reused 42
Unpacking objects: 100% (42/42), done.
[root@javabook /]# cd javabook-maven-example/

これで準備終わり。書籍の内容に入る。(書籍にはEclipseの手順とMavenの手順の両方が
掲載されているが、この記事ではMavenの手順のみ記載するので、章番号はとびとび。)

13-1-2 Mavenの基本的な利用方法

[root@javabook javabook-maven-example]# mvn package
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building javabook-maven-example 1.0-SNAPSHOT
[INFO] ---------------------------------------------------
〜中略〜
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.java.book.app.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.191 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
〜中略〜
[INFO] Building jar: /javabook-maven-example/target/javabook-maven-example-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1:13.426s
[INFO] Finished at: Tue Nov 28 14:13:10 UTC 2017
[INFO] Final Memory: 18M/157M
[INFO] ------------------------------------------------------------------------

man packageを実行するとビルドが始まって、問題なければBUILD SUCCESSが表示される。
ログを見ていて、とくに設定した覚えはないのにテストが実行されていることに気づいた。
そこでちょっと検証してみたところ、pom.xmldependencyJUnitの記載があるかつsrc/test配下にテストファイルがあると、mvn packageでテストが実行されるように見えた。
pluginではJUnitを指定しておらず、dependencyを指定しただけなのに、テストが実行されて驚いたが、そんなものなんだろうか。
mvn installを実行するとローカルリポジトリにビルド成果物が登録される。ログには以下の通り出力される。

[INFO] Installing /javabook-maven-example/target/javabook-maven-example-1.0-SNAPSHOT.jar to /root/.m2/repository/com/java/book/app/javabook-maven-example/1.0-SNAPSHOT/javabook-maven-example-1.0-SNAPSHOT.jar
[INFO] Installing /javabook-maven-example/pom.xml to /root/.m2/repository/com/java/book/app/javabook-maven-example/1.0-SNAPSHOT/javabook-maven-example-1.0-SNAPSHOT.pom

13-2-4 APIドキュメントを作成する

APIドキュメント確認

pom.xmlmaven-javadoc-pluginの設定を記述してmvn siteを実行する。
生成されたHTMLをhttpdのドキュメントルートに配置する。

[root@javabook javabook-maven-example]# cp -rp target/site/apidocs/* /var/www/html/
[root@javabook javabook-maven-example]# 

ブラウザでhttp://localhost:8080にアクセスしてAPIドキュメントを確認する。
f:id:sonomirai:20171128234402p:plain

13-3-3 Mavenによるフォーマットチェック

pom.xmlmaven-checkstype-pluginの設定を記述してmvn clean package siteを実行する。
生成されたHTMLをhttpdのドキュメントルートに配置する。

[root@javabook javabook-maven-example]# rm -rf /var/www/html/*
[root@javabook javabook-maven-example]# cp -rp target/site/* /var/www/html
[root@javabook javabook-maven-example]# 

ブラウザでhttp://localhost:8080にアクセスする。 f:id:sonomirai:20171202214806p:plain

Project ReportsのCheckstyleをクリックしてレポートを見る。けっこうErrorがある。。。 f:id:sonomirai:20171202214811p:plain

とりあえずErrorを0にするよう修正したが、あまり意味のある変更とは思えなかった。
Checkstyleの使い方は今度の課題かな。。。 f:id:sonomirai:20171202215438p:plain

13-4-4 Mavenによるバグチェック

pom.xmlにfixbugs-maven-pluginの設定を記述してmvn clean package siteを実行する。
生成されたHTMLをhttpdのドキュメントルートに配置する。

[root@javabook javabook-maven-example]# rm -rf /var/www/html/*
[root@javabook javabook-maven-example]# cp -rp target/site/* /var/www/html
[root@javabook javabook-maven-example]# 

ブラウザでhttp://localhost:8080にアクセスし、FindBugsのレポートを見る。 f:id:sonomirai:20171202220406p:plain

13-5-3 テストを実行する

書籍だとEclipseで実行しているところを、Mavenで実行してみる。
先ほどまでのリポジトリ内に、書籍に掲載されているテスト用プログラム(わざとバグを入れてある)とテストコードを作成する。
mvn clean package siteを実行するとテストがコケてビルドに失敗する。
日本語が正しく出力できていない。。。そこは今後の課題。。。

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.java.book.app.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.101 sec
Running acroquest.java.junit.GreetingTest
Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.019 sec <<< FAILURE!

Results :

Failed tests:   getMessage_\u591C\u958B\u59CB(acroquest.java.junit.GreetingTest): 
  getMessage_\u663C\u958B\u59CB(acroquest.java.junit.GreetingTest): 

Tests run: 4, Failures: 2, Errors: 0, Skipped: 0

コード修正して再実行するとテストに成功する。
JUnitの結果もCheckstyle, FindBugsと同様にレポート出力されるかと思ったが、されなかった。
実際はJUnitの結果はJenkinsで出力させることになるので、気にせず次に進む。
次の章に向けて、Gitのローカルリポジトリをリモートリポジトリにコミットしておく。

13-6-2 Jenkinsの環境を準備する

書籍だとWindowsにJenkinsをインストールしているところを、DockerのJenkinsコンテナを立ち上げて利用する。

Jenkinsコンテナ準備

Jenkinsコンテナを立ち上げて、Mavenをインストールする。

$ docker run --name javabook_jenkins -h javabook -u 0 -d -p 8081:8080 -p 50000:50000 jenkins
$ docker exec -it javabook_jenkins /bin/bash
root@javabook:/# apt-get update; apt-get install -y maven

Jenkins状態確認

ブラウザでhttp://localhost:8081にアクセスして以下の画面が表示されることを確認する。 f:id:sonomirai:20171202224921p:plain

Jenkins設定作業

書籍に掲載された方法で設定していく。(「Create First Admin User」画面は、どうもユーザが
作成できなかったので、「Continue as admin」を選択して先に進んだ。)

13-6-3 Jenkinsでビルドを実行する

書籍の手順通り設定する。ソースコードの取得元の設定では、先ほどコミットしたリモートリポジトリを設定する。 f:id:sonomirai:20171202231531p:plain

とくに問題なくビルド成功するはず。 f:id:sonomirai:20171202231754p:plain

13-6-4 Jenkinsでレポートを作成する

書籍の手順通り設定すると、Checkstyle, FindBugs, JUnit, カバレッジのレポートが出力される。
おお、こんな感じの画面を見てみたかった。よかった。 f:id:sonomirai:20171202233008p:plain

JUnitの結果もこんな感じで表示される。

f:id:sonomirai:20171202233048p:plain

さいごに

Java本格入門の13章「周辺ツールで品質を上げる」をdocker環境で写経する手順について書いた。
JavaアプリってWindows上のEclipseで開発してLinuxで動作させることが結構あると思うが、
これまで環境ごとの周辺ツールの使い方やツール同士の連携のさせ方がわかっていなかった。
13章を写経することで、その辺りの使い方がはっきり理解できてよかった。

ゼロから作るDeep Learning 5.7.2項のTwoLayerNetを理解するために書いた図

ゼロから作るDeep Learning 5.7.2項のTwoLayerNetの処理が、初見では理解できなかった。
そこで書籍の内容を復習して図示してからTwoLayerNetを読んだところ、かなり理解が進んだので、その時書いた図を残しておく。f:id:sonomirai:20170923205219p:plain

TwoLayerNet理解ためには、ReLU、Sigmoid、Softmaxは各層の活性化関数で、Affineは層の間の重み(重みは行列で表される)の内積ということがイメージできている必要があると思うが、自分は最初そこがイメージできていなくて苦戦したので、そこをサポートするような図を描いたつもり。

Selenium3をMacで動かす

Selenium使うことになったので、Selenium実践入門読みながらインストールしようと思ったら、Seleniumのバージョンが3に上がってて、パッケージ構成から違ってて涙目になった。
ググったところ、下記の記事が参考になったが、一部つまづくところがあったので、自分のためにメモを残す。
なお対象のブラウザはFirefoxChrome

Selenium入門その6[Selenium3でWebDriver(Java/Junit4)の環境を作成しEdge,Chrome,Firefoxで確認してみる]

環境情報

ソフト バージョン
Mac OS X El Capitan 10.11.6
Selenium 3.4.0
mozilla Firefox 54.0.1
geckodriver 0.18.0
Google Chrome 59.0.3071.115
chromedriver 2.30

前提

  • Eclipseがインストール済みであること
  • Firefoxがインストール済みであること
  • Chromeがインストール済みであること FirefoxChromeがインストール済みであることの確認は、LaunchpadでFirefox, Chromeで検索して、プログラムが見えればOKだと思う。

環境構築

基本は参考ページ通りでいいが、chromedriverはPATHの通ったディレクトリに配置しておかないと、実行時にエラーになるっぽい。
自分は/usr/local/bin配下に配置した。

実行

参考ページのソースを以下のように微修正して実行した。

package sample.selenium3;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class Selenium3Sample {

    @Test
    public void firefoxTest() {
        System.setProperty("webdriver.gecko.driver", "./exe/geckodriver");
        DesiredCapabilities cap = DesiredCapabilities.firefox();
        cap.setCapability("marionette", true);
        WebDriver driver = new FirefoxDriver(cap);
        driver.navigate().to("http://www.google.com");
        driver.findElement(By.id("lst-ib")).sendKeys("Selenium3");
        driver.findElement(By.name("btnK")).click();
        if(driver!=null) {
            driver.close();
        }
    }
    
    @Test
    public void chromeTest() {
        System.setProperty("webdriver.chromedriver.driver", "");  //chromedriverはPATHの通っている/usr/local/binに配置
        WebDriver driver = new ChromeDriver();
        driver.navigate().to("http://www.google.com"); 
        driver.findElement(By.id("lst-ib")).sendKeys("Selenium3");
//     driver.findElement(By.name("btnK")).click();              //動かなかった
        driver.findElement(By.id("lst-ib")).sendKeys(Keys.ENTER);    //動いた
        if(driver!=null) {
            driver.close();
        }
    }
}

参考ページからの変更点は3点。

  1. DesiredCapabilities周りの設定の削除
    なしでも動いたので削除した。Seleniumのバージョンが上がって、なしで動くようになった?
  2. chromedriverのPATH指定
    exe配下にchromedriver置いてもちゃんと読んでもらえず、エラーになってしまう。
    PATHの通ったところにchromedriverを配置したら読んでもらえたので、setPropertyの第二引数は明示的に空にしておいた。
  3. chromedriverでの検索実行
    「driver.findElement(By.name(“btnK”)).click();」だと動かなかったので、とりあえず「driver.findElement(By.id(“lst-ib”)).sendKeys(Keys.ENTER);」にした。

まとめ

参考ページをベースにちょっと変更をしたら、Selenium3を動かすことができた。参考ページに感謝。