作ってみた

AndroidでマルチリンガルTTS(音声合成)アプリ

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - AndroidでマルチリンガルTTS(音声合成)アプリ
このエントリーをはてなブックマークに追加
はじめましてこんにちは
9月に入社しました、Androidアプリ開発者のたかなです。

今日はAndroidの搭載機能である「TTS(音声合成)」を用いた マルチリンガルTTSアプリを作ってみたいと思います!

20111228-171243



TTSとは


TTSは Text To Speech の略です。読んで字のごとく、音声合成を指します。

初音ミクなどのVocaloid、駅のアナウンス、ロボットに搭載されている音声を耳にしたことがある人も多いでしょう。棒読みちゃんを使っている人もいるかもしれませんね。

TTSは「動的に変化する情報をリアルタイムにしゃべってほしい」「状況に応じて素早くアナウンスしたい」「いつでも、どこでも、手間をかけずに情報を発信したい」といったニーズに対応するよう作られ、郵便局や病院の番号札の読み上げなどから、近年では肉声に近いなめらかな発音のモデルも増えてきました。


AndroidにTTSが標準搭載


Android 1.6から、TTS機能が標準搭載されています。
ただし、音声を合成するためにはボイスデータと呼ばれる音声のパーツの集合(アプリ形式)をインストールする必要があり、そのままでは使うことができません

今回のアプリではその点を加味して、ボイスデータの有無をチェック、なければマーケットの該当アプリへ遷移する、という機能も付加しています。


AndroidTTSの対応言語


現在、AndroidTTSの対応言語は「英語(イギリス英語を含む)」「フランス語」「ドイツ語」「イタリア語」「スペイン語」の5つです。 自分のAndroid端末がどの言語をしゃべれるかは、「設定」→「音声入出力」→「テキスト読み上げの設定」→「言語」で確認できます。

残念ながら日本語非対応とのことで、日本語の合成を実現するためには別のライブラリが必要です。


AndroidTTSを使ってみる


SDKの「TextToSpeech」クラスを使います。
リファレンスはこちら TextToSpeech - Android Developers

しゃべらせること自体はとても簡単で、 speakメソッドの引数に読んでほしいテキストを与えるだけです。
各言語の設定を行うにはsetLanguageメソッドでLocaleを指定します。
今の設定がどの言語か?を調べるためにはgetLanguage()しましょう。

また、自分以外の人の端末でアプリを使って欲しい場合は、その端末の対応言語を調べる必要があります。
これはisLanguageAvailableメソッドにLocaleを指定することで、ステータスがTextToSpeechの定数として返ってきます。

例)端末がアメリカ英語のTTSに対応可能かを判断するためには
if (mtts.isLanguageAvailable(Locale.US) >= TextToSpeech.LANG_AVAILABLE) {
   //対応可能だった時の処理
}

となります。 TextToSpeech.LANG_AVAILABLEは「言語が使用可能である」ことを指していて、 ステータスがLANG_AVAILABLE以上の状態であれば合成が可能です。

余談ですが、サポートされていないLocaleならLANG_NOT_SUPPORTED、必要なボイスデータがインストールされていないとLANG_MISSING_DATAが返ってくるので、必要に応じて処理を加えましょう。


作ってみよう


アプリの仕様です。

  1. はじめにボイスデータがあるかどうかチェックする

  2. ヘルプボタンを押すとガイド音声が流れる

  3. デフォルトはアメリカ英語

  4. テキストを入力して「TextToSpeech!」ボタンを押すと合成し再生

  5. 「Laguage」ボタンで言語の切り替えが可能



サンプルコード


TextToSpeechActivity.java

package jp.android;

import java.util.Locale;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class TextToSpeechActivity extends Activity  implements TextToSpeech.OnInitListener {

    private static final int CHECK_TTS = 682730935;
    private Button mSpeakButton;
    private TextToSpeech mTts;
    private EditText et;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        this.checkTTS();

        mTts = new TextToSpeech(this, this);

        this.mSpeakButton =
            (Button) findViewById(R.id.speak_button);
        et = (EditText)findViewById(R.id.inputText);
        mSpeakButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                startTTS(et.getText().toString());
            }
        });

    }

    /**
     * TTSエンジンがインストール済かどうかを判定
     */
    private void checkTTS() {
        Intent intent = new Intent();
        intent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        startActivityForResult(intent, CHECK_TTS);        
    }

    // チェックの結果を受け取る
    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == CHECK_TTS) {
            if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
                //インストール済ならウェルカムメッセージを発話
                startTTS("Welcome to TTS application, Let`s start!");
            }
            else {
                //インストール無しなら
                Intent install = new Intent();
                install.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
                startActivity(install);
            }
        }
    }

    /**
     * 合成を実行
     * @param text 読み上げたいテキスト
     */
    private void startTTS(String text) {
        mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
    }

    /**
     * ヘルプガイドを読み上げる
     * @param v
     */
    public void startGuide(View v) {
        mTts.setLanguage(Locale.US);
        startTTS(getString(R.string.guide));
    }

    /**
     * 言語選択ボタン
     * @param v
     */
    public void onSelectLang(View v) {
        AlertDialog.Builder ab = new AlertDialog.Builder(this);
        String[] str = {"English","French","German","Italian","United Kingdom"};
        ab.setItems(str, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int i) {
                switch (i){
                    case 0:
                        mTts.setLanguage(Locale.US);
                        break;
                    case 1:
                        mTts.setLanguage(Locale.FRENCH);
                        break;
                    case 2:
                        mTts.setLanguage(Locale.GERMAN);
                        break;
                    case 3:
                        mTts.setLanguage(Locale.ITALIAN);
                        break;
                    case 4:
                        mTts.setLanguage(Locale.UK);
                            break;
                }
                
            }
        });
        ab.show();
    }

    public void onInit(int status) {        
        if (status == TextToSpeech.SUCCESS) {
            if (mTts.isLanguageAvailable(Locale.US) >= 
                    TextToSpeech.LANG_AVAILABLE) {
                //アメリカ英語に設定
                mTts.setLanguage(Locale.US);
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mTts.shutdown();
    }
}



20111228-171254

非常に簡単にマルチリンガルなTTSアプリが作れました。


注意点


speakメソッドは非同期なので、音声の合成処理・再生中はタスクの状態に応じた処理が必要です。特に長い文章を読ませたい場合(入力される文字数を制限しない場合)は注意しましょう。


サンプルプロジェクトのソース


スペースの関係で全部のソースは載せられないので、こちらに置いておきます。
https://www.assembla.com/code/texttospeech/git/nodes


まとめ


AndroidTTSを使うと、手軽に音読アプリが作れます。
合成から音声再生までのレスポンスが速いので、ちょっとした外国語のトレーニングに使えそうですね。
定型文をあらかじめ入力しておけば、海外旅行にも役立ちそうです。

また、今年1月から、全てのAndroid端末で 音声対話型コンシェルジュサービスnetpeople : aが利用可能になるとのことなので、TTSが日本語サポートされたら端末同士の会話が実現するかもしれません。


<おまけ>Androidで使える日本語TTSライブラリ


日本語で音声合成してみたい!という方向けに公開されているライブラリです。
有料のものがほとんどなので、気軽に試せないのが少し残念ですね。

音声合成エンジン "Galatea Talk"を用いたライブラリ

Androidを「貧血チェックガジェット」化する!

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - Androidを「貧血チェックガジェット」化する!
このエントリーをはてなブックマークに追加
はじめまして。今回のTechブログを担当する@ianim4です。
まだ、本当の名前はありません。


今回のお題

Tech ブログ、スマフォシリーズもだいぶ回を重ねて参りました。今回は、そろそろ何か作ってみましょうと言う事で技術のライブドアとして何がいいかなと、考えてみます…

思いつかないときは、逆立ちをして、数秒待ちます。

はっちゃけた!(古
貧血チェックガジェットを作ることになりました。

どういったものかと言うと、貧血になった人がいた場合に、Androidが知らせてくれると言う画期的なものです。これは、夏の体育館、校長先生のありがたいお話の時にはまず間違いなく必要とされるガジェットでしょう。

実装に向けて

貧血は、「血液中の赤血球が乏しくなること」とのことなので、血液中の赤血球濃度を測ればいいですね。Androidには、いろいろなセンサーがあるらしいのでこの中に役に立ちそうなものがあるのか確認してみました。

え〜と。

加速度センサー
明るさセンサー
ジャイロセンサー。

へ〜。

地磁気センサー
傾きセンサー

ふむふむ。

圧力センサー

近傍センサー


あれ?

温度センサー。

これで、最後か…



今回の企画は、方向性が不確かなものとなったため一時休止となりました。
ご迷惑をおかけします。申し訳ありません。



追記

「貧血チェックガジェット」の企画を休止とさせて頂いたわけですが、ちゃんとやりなさい、と、言う事なので、この記事を追記させて頂きます。

今回は、完成しました!

用意したものは、Android本体、アイマスク、セロテープ、Androidの加速度センサーを使用した自己流貧血判定アルゴリズム!? です。


作り込み


hinketsu_pic1
自己流貧血判定アルゴリズム搭載のガジェットアプリはこんな感じ


hinketsu_pic2
そして、アイマスク。


hinketsu_pic3
検証機のAndroidと、アイマスクをセロテープで固定する。


hinketsu_pic4
頭に装着できる様になった。


検証

hinketsu_pic5
あ! このガジェットを装着したままの人が貧血で倒れている。


BlogPaint
近くに寄ってみると、アプリ上になにかトーストが


BlogPaint
もっと寄ってみる。わかりずらい…


hinketsu_pic8
エミュレータのスクリーンショットで。


プログラムについて

自己流貧血判定アルゴリズム!? ということで加速度センサーを使用しました。まあ検索してもらえばわかりますが、加速度センサーについては沢山実装方法が載っています。ここで、あえて私が書くのも恐縮ではありますが、解説させて頂きます。

加速度センサーは、以下の様な感じで利用してみました。

public class Main extends Activity {
  static final String TAG = "Main";
  private float[] mPoolValues;

  public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mPoolValues = null;
    //-------------------
    // 加速度センサーを定義        
    //-------------------
    SensorManager sensorManager =
        (SensorManager)getSystemService(Context.SENSOR_SERVICE);
    // 機種に加速度センサーがあるか確認し存在していた場合、
    //イベントリスナーを定義する
    List<Sensor> sensors =
        sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
    if(sensors.size() > 0){
      sensorManager.registerListener(
        new SensorEventListener() {
          public void onAccuracyChanged(Sensor arg0, int arg1){}
          public void onSensorChanged(SensorEvent ev){
            // 初回 加速度の3軸値それぞれをとっておく
            if(mPoolValues == null){
              mPoolValues = new float[]{
                                ev.values[0],
                                ev.values[1],
                                ev.values[2],
                            };
              return;
            }
            // 前回と今回の加速度差分の大きさを取得。
            float ac0 = ev.values[0] - mPoolValues[0];
            float ac1 = ev.values[1] - mPoolValues[1];
            float ac2 = ev.values[2] - mPoolValues[2];
            float d = (float) Math.sqrt(
                                  ac0 * ac0 + 
                                  ac1 * ac1 + 
                                  ac2 * ac2);
            // 加速度の差分の大きさが15を超えたら、この人貧血。
            if(d > 15){
              Toast.makeText(Main.this,
                             "貧血になりました。",
                             Toast.LENGTH_LONG).show();
            }
            // 今回の加速度を保持
            mPoolValues[0] = ev.values[0];
            mPoolValues[1] = ev.values[1];
            mPoolValues[2] = ev.values[2];
          }
        },
        sensors.get(0),
        SensorManager.SENSOR_DELAY_NORMAL// Sensorの通知感覚→標準
      );
    }
  }
}


加速度センサーは、下記の様にSenserManager#getSensorListメソッドで取得します。
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
この、メソッド。その他センサーの取得も出来ます。
Sensorクラスの定数で指定します。
Sensor.TYPE_ACCELEROMETER加速度センサー
Sensor.TYPE_LIGHT明るさセンサー
Sensor.TYPE_GYROSCOPEジャイロセンサー
Sensor.TYPE_MAGNETIC_FIELD地磁気センサー
Sensor.TYPE_ORIENTATION傾きセンサー
Sensor.TYPE_PRESSURE圧力センサー
Sensor.TYPE_PROXIMITY近傍センサー
Sensor.TYPE_TEMPERATURE温度センサー

戻り値はList型なので、同一センサーで複数存在するのもあるし、ないのもあるということなのでしょうか。検証機のAndroidでは、温度センサーやジャイロセンサーはありませんでした。

次に、SensorManager#registerListenerメソッドでイベントリスナーを登録します。今回は、onSensorChangedのコールバックメソッドで貧血判定ロジックを実装です。

コールバックが来るたびに加速度の前回との差分を計算しその値が指定の閾値より大きい時、つまりは貧血で転倒したときの反動をトリガーとして、アラートを出しています。

加速度の3軸の値それぞれ、前回の差分を求め
その差分で構成されているベクトルの大きさを求めます。
二乗の和のルートをとり、これを加速度の前回との差分としました。
※回転による遠心力等については、今回特に考えていません。

※今回のサンプルプロジェクト一式です。
ダウンロードはこちら!


まとめ


倒れた勢いを検知して、貧血状態をチェックできた。優れものだな、このガジェット。

夏休み自由研究 - iPhoneでいきもの図鑑を作る

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - 夏休み自由研究 - iPhoneでいきもの図鑑を作る
このエントリーをはてなブックマークに追加
おつかれサマーです。開発部モバイルGの井上です。ケータイライブドアの各サービス、iPhoneアプリ開発等を担当しています。

さて、夏休みもあと数日になりました。全国の小学生の皆さんは、夏の宿題に追われている頃ではないでしょうか。 え、まだ手をつけてない? 大丈夫、たいていそんなものです。これからが勝負ですね。

たとえば昆虫採集なんか、比較的お手軽でよいんじゃないでしょうか。近くの公園で虫を捕まえて、お中元の箱かなんかに並べて学校に持っていけばオッケー。 ただ、研究テーマとしては古典的すぎて今さらという感じですね。虫をピンで刺すのもかわいそうだし。

そこで今回は、iPhoneアプリでいきもの図鑑を作ってみます。エコかつIT、スマートですね。 先生もびっくり、クラスのあの子にも超アピールです。

iPhoneアプリは、作り込みをはじめると時間がかかりますが、簡単なアプリなら工作感覚で手軽に組むことも可能です。 カメラやGPSも、最小限のコードで利用できるAPIが用意されています。 カメラでいきものの写真を撮ると、位置情報つきで一覧できて、地図にプロットできる、というライブな図鑑アプリを作ってみます。

プログラムなんかできない? 問題ナッシングです。英語がちょっと読めればプログラムもだいたい読めます。

それでは始めてみましょう。


■必要なもの


○開発機
Mac(9.4万円〜)
ノートでもデスクトップでも可。

○開発ソフト
XCode(無料)
アップルのサイトからDLできます。UIデザインをするInterfaceBuilderと、動作確認用のiPhoneシミュレータも同梱されています。

○できれば用意したいもの
iPhone か iPod Touch(2.5万円〜)
iPhone Developer Program($99)
シミュレータである程度代用できます。本気で開発するときにはお父さんに貸してもらいましょう。

MacでXCode、InterfaceBuilder、iPhoneシミュレータを立ち上げ、実機(ある場合)を接続したら、準備は完了です。


■画面図を描く


まずアプリ画面の全体図をかんたんに描いてみます。チラシの裏でオッケーです。 ボタンやバーの配置、ナビゲーションなどを、途中で迷わないように決めておきます。

02_01

今回は、いきものの写真を撮り、マップに表示させるということで、大きく画面は2つです。 下にタブバーを置いて、カメラとマップをそれぞれ左右のタブにあてました。 写真を撮るときには、カメラビューの右上のボタンを押すと、カメラユニットが立ち上がります。

全体の画面が決まったら、Macを立ち上げて製作に入ります。


■ビューをデザインする


Xcodeで新規プロジェクトを作ってアプリを作っていきます。 メニューから、ファイル > 新規プロジェクト > iPhone OS > Tab Bar Application を選択すると、タブバーを使ったアプリケーションのスケルトンが作られます。

03_01

項目が山ほどあってイヤンな感じですが、とりあえず左カラムのResourcesとClassesのフォルダに注目です。

Resourcesの中に、FirestView.xib・SecondView.xibというファイルがあります。それぞれ、タブで切り替わる2つの画面デザインを定義するファイルです。InterfaceBuilderで編集します。 Classesの中にあるのが、動作のロジックを記述していくプログラムファイルです。

プログラムの処理は画面単位でまとめます。FirstViewでの処理は、FirstViewController.mに書きます。同名の.hというファイルがありますが、これはヘッダファイルといって、変数やメソッドの定義を書くところです。

このスケルトンはもうアプリとして動きます。「ビルドと実行」という緑のボタンを押してみてください。 シミュレータが立ち上がり、アプリが起動します。タブバーで2つの画面を切り替えられます。

03_02

このひな形をもとに、ビューのデザインやプログラムコードを追加して、アプリを作っていきます。

まず、FirstViewとSecondViewにカメラのボタンと地図を配置してみましょう。

FirstView.xibをダブルクリックすると、InterfaceBuilderで画面のプレビューがでてきます。 このプレビューにバーやボタン、画像や地図などを配置することで、画面をデザインすることができます。

さまざまなパーツが並んだ LibraryペインからUIをドラッグ&ドロップします。レゴの組み立ての感覚ですね。 パーツの色・サイズ・アイコンの種類などは、View Attributesペインで設定できます。

04_01
04_02

続いてSecondViewにはバーとマップを配置します。

04_03

マップと位置情報を使うためには、追加の設定が必要です。 Xcode左カラムのFrameworkフォルダをCtrl+クリックして、「追加 > 既存のフレームワーク…」から MapKit.frameworkとCoreLocation.frameworkを選んでください。

1回保存して、緑のボタンでビルドしてみましょう。

04_04 04_05

ボタンやマップが出てきます。 この状態でもう、マップをスクロールしたり拡大することもできます。

1行もコードを書いてませんが、それらしくなってきましたね。


ロジックを書く


いよいよプログラミングです。 何をしたらどうなるかというロジックの部分を、XCodeの中に書いていきます。

今回必要なプログラムの処理は4つです。意外と少ないですね。

ファーストビューの処理
・カメラボタンを押したとき → カメラが立ち上がって写真が撮れる
・写真が撮れたとき → 写真の画像データ、および位置情報・タイトル・日付のデータを保存する
・データがあるとき → テーブルに画像とタイトルの一覧を表示する

セカンドビューの処理
・データがあるとき → マップに画像と位置を示すピンを表示する

それぞれ数十行程度の簡単なプログラムです。 順番に見ていきます。

まずカメラボタンを押したときのコードです。分からなくてもいいので、ざっと見てみてください。

- (IBAction)pushedCameraButton {
	
	self.imagePickerController = [[[UIImagePickerController alloc] init] autorelease];
	self.imagePickerController.delegate = self;
    self.imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
	self.imagePickerController.showsCameraControls = YES;
	[self presentModalViewController:self.imagePickerController animated:YES];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
	
    UIImage *takenPhoto = [info valueForKey:UIImagePickerControllerOriginalImage];
	[self dismissModalViewControllerAnimated:YES];
	[self performSelector:@selector(showCameraInputView:) withObject:takenPhoto afterDelay:1.0];
}

プッシュカメラボタンとあって、そのあとカッコでくくられてますね。 ボタンを押したら、カッコの中の処理が走るということです。 イメージピッカーというのがiPhoneでのカメラ機能を受け持つAPIです。 いくつか設定をして、最後の presentModalViewController で画面に表示させています。カメラの画面がにゅっと下から出てくることになります。

ふたつめのカッコで、シャッターが押されたときの画像が返ってきます。takenPhoto というのが画像データを表します。

ここでは、タイトルを入力するフォームに takenPhoto を渡しています。フォーム上でタイトルなどを入れて、保存ボタンを押すと、データが一式保存されます。

次に保存するところのコードです。

タイムスタンプ + 乱数のidを決めて、その名前で写真のpngファイルを保存、さらにid・日時・位置情報・タイトル・ノートをディクショナリーと呼ばれるデータホルダーの中に突っ込んで、それを保存しています。ちょっと長いですが、だいたいの流れが分かればオッケーです。

- (IBAction)save {
	
	NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
	NSDate *date = [NSDate date];

	// ID
	srand(time(nil));
	int num = rand() % 100;
	int sec = (double)[date timeIntervalSince1970];
	NSString *anId = [NSString stringWithFormat:@"%d%d", sec, num];
	[dictionary setObject:anId forKey:@"id"] ;
	
	// 位置情報
	CLLocation *location = [locationManager location];
	if (!location) return;
	CLLocationCoordinate2D coordinate = [location coordinate];
	[dictionary setObject:[NSNumber numberWithDouble:coordinate.latitude] forKey:@"latitude"];
	[dictionary setObject:[NSNumber numberWithDouble:coordinate.longitude] forKey:@"longitude"];
	
	// 日付
	[dictionary setObject:date forKey:@"date"];
	
	// タイトルとノート
	[dictionary setObject:titleTextField.text forKey:@"title"];
	[dictionary setObject:noteTextView.text forKey:@"note"];
	
	IkimonoAppDelegate *appDelegate = (IkimonoAppDelegate *)[[UIApplication sharedApplication] delegate];
	
	// 画像の保存
	NSData *pngPhoto = UIImagePNGRepresentation(photo);
	[pngPhoto writeToFile:[NSString stringWithFormat:@"%@/photo/%@@2x.png", [appDelegate documentPath], anId] atomically:NO];
	
	NSData *pngTumbnail = UIImagePNGRepresentation(thumbnail);
	[pngTumbnail writeToFile:[NSString stringWithFormat:@"%@/thumbnail/%@@2x.png", [appDelegate documentPath], anId] atomically:NO];
	
	// Dictionaryの追加
	[appDelegate.ikimonoArray addObject:dictionary];
	[appDelegate saveArray];
	
	[self dismissModalViewControllerAnimated:YES];
}

データが保存できればあとは表示するだけですね。マップへのピンの表示は、アノテーションというのを作って、そこに先ほどのディクショナリーからタイトルや位置情報をセットしてやって、マップに渡してやります。

- (void)addAnnotation {
	NSDictionary *dictionary = [appDelegate.ikimonoArray objectAtIndex:i];
	Annotation *annotation = [[Annotation alloc] init];
	annotation.latitude = [dictionary objectForKey:@"latitude"];
	annotation.longitude = [dictionary objectForKey:@"longitude"];
	annotation.title = [dictionary objectForKey:@"title"];

	NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
	[outputFormatter setDateFormat:@"MM/dd HH:mm"];
	annotation.subtitle = [outputFormatter stringFromDate:[dictionary objectForKey:@"date"]];
	[self.mapAnnotations addObject:annotation];
	[self.mapView addAnnotation:annotation];	
}

という感じで、必要なプログラムを追加していきます。

05_01

処理が書けたら、InterfaceBuilderで配置したパーツと、プログラムを結びつけます。 InterfaceBuilderのFile's OwnerをCtrl+クリックすると、先ほどのpushedCameraButtonがでてくるので、それをカメラのボタンにドラッグしてやります。

これでデザインとロジックが結合したことになります。 こんな感じでInterfaceBuilderとXCodeを行ったりきたりしていると、アプリがだんだん出来あがってきます。

さて、最後に仕上げです。 アプリ名やアイコン画像を設定します。アイコンはきれいなのを作りたいものです。 絵のうまい友だちに描いてもらうといいですね。

自分はあまりうまくないです。

05_02

完成!!!


いきものを撮影


では、近所の小金井公園にいきものを探しに行ってみます。 小金井公園は79万平米、東京都で2番目に広い公園です。園内には広い芝生や雑木林、池などもあります。

草むらの陰にセミの抜け殻を発見!

06_01

民家の塀のアサガオ。

06_02

池にはカメ。

06_03

オケラを見つけてしまいました。こいつはレアです。

06_04

図鑑らしくなりました。 マップのタブを確認すると、見つけた場所にピンが落ちてます。

06_06 06_05

ばっちりですね!

写真を撮りまくって、自分だけのいきもの図鑑を作ってみてください。 ネットワーク機能を追加して、友だちと共有しても楽しそうです。

といういわけで、夏の自由研究で作ってみたいちょっとしたiPhoneアプリのススメでした。

iPhoneプログラミングはMac1台から始められますし、GPS・カメラ・加速度センサーといった最新のモバイル機能を手軽に利用できます。 工作スピリッツ燃える小中高生の皆さんは是非チャレンジしてみるといいと思います。もちろん、大人の自由研究というのもアリですね。

今回のサンプルコードはこちらからダウンロードできます。

ライブドア流自作サーバ

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - ライブドア流自作サーバ
このエントリーをはてなブックマークに追加
livedoorポータルサイト等のインフラを担当している片野です。
今回は若干いまさら感がありますが、弊社にて運用している自作サーバをご紹介します。
(検証機以外は弊社内で組み立ててないので、正確には自作してないんですがw)


■背景


去年は特に自作サーバが盛り上がっていましたし、早い段階で「うちも作るか!」という展開はあったんですが、弊社では以下のような事情もあり、着手していませんでした。

・自社データセンタでの運用なため、その他顧客と運用が大きく変わるようなサーバを投入すると運用が煩雑になる。
・ポータルのサービスだけでも3000台近いサーバがあり、規模的にも運用に手がかかるサーバを入れるのは非現実的。
・ボリュームメリットが出せるため、コスト面での自作メリットはそれほど大きくない。
そもそも自作で間に合う台数じゃないw

(人的な)運用コストや調達コスト面ではあまりメリットがないのですが、ある事情により、年末辺りから自作サーバを再検討していました。

その事情とは…


ラックの電力が足りない


まあ、どこの企業様も悩みは同じだと思うのですが、1ラック辺りに詰め込むメモリ量やストレージ量で考えると、サーバベンダ機ではとにかく電力がネックになります。

というわけで、以下のような条件で設計を進めました。

・サーバ用CPUは電力が高い(値段も)。省電力なCPUを使いたい。
・仮想化して効率良く使うにはメモリ16GBは欲しい。
・安く容量を増やせるストレージが欲しい。→3.5インチが有利
・ホットスワップは必須。
・電力的に詰め込むのが不可能なので、1Uハーフにはこだわらない。
・蓋なしのケースはダメ。ラックに固定できること。燃えそうだったり、感電しそうなケースももちろんダメw


■特徴


下記サーバが弊社で数ヶ月前から運用している自作(パーツの)サーバです。

sv03
※2010年5月現在で100台ほど稼働中。


今投入しているスペックは、基本的に以下のような構成です。
・CPU:Athlon II X4 605e
・Mem:16GB
・HDD:250GB(RAID1)

詳細は写真から判断頂ければ。
全然特殊なパーツはないですが。


性能と電力で言うとAM3が非常に優秀なので、今は省電力モデルのAthlonやPhenomを使ってます。
(省電力モデルのXeonもあるけど高すぎたので) 

下表は弊社でメインに使っているサーバベンダ機との比較結果です。





サーバ処理リクエスト数負荷時消費電力処理リクエスト数/Watt
X3250(Core2Duo x1, 8GB)355 req/s110 W3.23 req/W
X4150(Core2Quad x2, 24GB)962 req/s230 W4.18 req/W
自作サーバ(AthlonII X4 605e x1, 16GB)495 req/s91 W5.44 req/W
自作サーバ(PhenomII X4 945 x1, 16GB)662 req/s115 W5.76 req/W

Xeonと比較すると電力的にかなり有利なのが分かると思います。


ケースは2Uハーフです。
2Uハーフを選択している理由としては、下記のような点です。

・パーツ選定の制約が少なく、ケースの使い回しが効く。
・3.5インチHDDが使えるので、容量単価が有利。


1Uハーフでないとマウントできないような台数は電力的に入らないため、パーツ選定の自由度を優先して敢えて2Uハーフにしています。

また、既存サーバのラックレールに乗るように微調整がしてあります。
(ラックマウントしやすい形にすることでオペさんに怒られないライフハックw)

大量にあるラックレールを使い回せてエコです!
sv02


■今後の課題


・Atom+2TBHDDでストレージサーバ構築

検証はしていますが、まだ投入はしてません。
ストレージは増加する一方ですし、コスト削減は至上命題ですね。


・Phenom II X6の検証

まだ発売されていませんが、95W版の6CorePhenomは電力的に優秀そうなので気になってます。
より電力的に美味しいCPUへ随時交換して行きたいので、省電力Xeonも含め、色々と検証はしています。


運用面での課題はありますが、サーバベンダ機にはないメリットもありますし、何よりパーツ選定は楽しいので(笑)
今後も適材適所で使い分けていく予定です。

Arduinoで事業部長を監視しCiao!

カテゴリ
ブックマーク数
このエントリーを含むはてなブックマーク はてなブックマーク - Arduinoで事業部長を監視しCiao!
このエントリーをはてなブックマークに追加
こんにちは、最近はモバイル向け新規サービスの開発を担当している栗原です。
今回のTechブログではMAKE系の話をご紹介したいと思います。

フィジカルコンピューティングとは

皆さんは『フィジカルコンピューティング』という言葉を御存知でしょうか。フィジカルコンピューティングとは、既存のパーソナル・コンピュータのグラフィカル・ユーザー・インターフェイス(ウインドウ,マウス,アイコンなど)を超えて、私たちの生活環境によりそった身体的なコンピュータのあり方を模索する研究の動向のことを言い(出典:はてなキーワード)、最近ではそれらの研究や実験を簡単に実現するための便利なハードウェアがいくつも存在します。

代表的なハードウェアとしては、ArduinoGainerFunnelなどがあります。これらは単純な入出力を供えた拡張可能な基板となっており、適当な電子部品をつなげることにより様々なことを実現できます。

今回は、この中でもArduinoという物を使ってみようと思います(ちなみに「アルドゥイーノ」と読むらしいです)。Arduinoは先に上げた3つのハードウェアの中でも最も人気のあるもので、今、巷で密かな人気が出ており、以前mixiさんの開発者ブログなどにも紹介されていました。

なにを作るか

さて、Arduinoを使ってなにを作ろうかと考えていて、ふと社内のある問題が思い浮かびました。それはある事業部長が忙しいが故になかなか席にいないため、話がしたくても運がよくないとなかなか会えないという問題です。

以前、弊社櫛井のブログのエントリ『ライブドアに行ってきた!』でもご紹介したように、事業部長のポーズ入り写真ボードで確認をしていました。 しかし毎回自分で上げ下げをしないといけなかったり、そもそも席が遠かったり階が違うと確認できないという問題があり、だんだん使われなくなってしまいました。

今回はこのような問題をArduinoとちょっとしたプログラムで解決したいと思います。

作る(MAKE!)

用意するのは、下記になります。
  • Arduino本体 1個 3200円
    説明不要ですね。Arduinoの本体になります。
  • Arduino用イーサネットシールド 1個 4200円
    Arduinoにイーサネットをつなぐためのシールドになります。シールドとは拡張キットみたいなものです。
  • NaPiOn(赤外線センサー) 1個 1600円
    この回路のメインとなります。NationalのNaPiOnという超小型赤外線センサーのAMN11112という型番のものです。
  • ブレッドボード 1個 200円程
    半田などを使わなくても結線することができるフィジカルコンピューティングにはかかせないボードです。このボードにワイヤー(線)を差すだけで回路を作ります。
  • ブレッドボード用ワイヤー 複数本 100円程
    ブレッドボードの穴に差しやすいワイヤーです。
  • 抵抗 1個 雀の涙
    中学校の頃の授業で見たことがある方もいるでしょう。普通の抵抗です。
これらを下記のように配線します。
この図はFritzingというArduinoなどの回路を作成するためのソフトウェアで作成しました。
circuit

簡単に回路図を説明すると、赤外線センサーNaPiOn(画面上部の灰色の電球のようなもの)の電圧入力端子(Vdd)にはArduinoから+5Vの電源をつなぎ、グランド(GND)はArduinoのGND(0V)につなぎます。そしてセンサーで判定した結果が出力される端子(OUT)は、デジタルピンの3番につなぐとともに途中から10Ωの抵抗を経由してGNDに落しています。 実際に配線した様子は下記のようになっています。左に白いポッチのようなものが見えると思いますが、こちらが赤外線センサーのNaPiOnになります。

arduino

次にこの回路を動かすためのスケッチを描きます(なおArduinoの世界では、Arduinoを動かすためのブログラムをスケッチと呼びます)。スケッチは基本的にはProcessingという言語で書きます(Rubyにradというプロジェクトがあり、Rubyでも書くことができます)。

スケッチの基本的な流れはsetup()を実行した後にloop()を実行しつづけます。このスケッチでは、setup()でネットワークの初期化を行なったあと、loop()で赤外線センサーからの値を取得して後述するPerlプログラムが動いているサーバに接続して、その値を渡します。
#include <Ethernet.h>
#include "Dhcp.h"

const int readPin = 3;
int sensorValue = 0;
int debug = 0;

byte server[] = { 192, 168, 0, 1 }; // Server that perl runs
byte mac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Sample mac
byte ip[4];
byte gateway[4];
byte subnet[4];

Client client(server, 7791);

void setup() {
  int result = Dhcp.beginWithDHCP(mac); // Get IP from DHCP
  
  if(result == 1) {
    Dhcp.getLocalIp(ip);
    Dhcp.getSubnetMask(subnet);
    Dhcp.getGatewayIp(gateway);
    
    Ethernet.begin(mac, ip, gateway, subnet); // Etheret init
    delay(100);
  } else {
    Serial.begin(9600);
    Serial.println("DHCP failed.");
  }
}

void loop() {
  sensorValue = digitalRead(readPin);    
  if (debug) Serial.println(sensorValue);
  if (client.connected() || client.connect()) {
    send_sensor_value(sensorValue); // Send sensor value
  } else {
    client.stop();
    Ethernet.begin(mac, ip, gateway, subnet); // Retry
  }
  delay(2000); // 2secs
}

void send_sensor_value(int sv) {
    client.print(sv); // Send value to server
    client.flush();
}

さて、あとはこのスケッチから事業部長がいるかいないかのデータを受けとり、Twitterや社内IRCで「いる?」という問い合わせに反応するプログラムを書くだけです。今回はPerlプログラムで下記のように書きました。

#!perl

use strict;
use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::Twitter::Stream;
use AnyEvent::HTTP;
use AnyEvent::IRC::Client;
use HTTP::Request::Common;
use Net::Twitter;

use constant STAY => 1;
use constant AWAY => 0;

my $condvar = AnyEvent->condvar;
my $i = 0;
my @statuses;

sub status_avg {
    my $one  = scalar( grep { /^1$/ } @statuses );
    my $zero = scalar( grep { /^0$/ } @statuses );
    return $one > $zero;
}

# Check stream on Twitter
AnyEvent::Twitter::Stream->new(
    username => 'TWITTER_USERNAME',
    password => 'TWITTER_PASSWORD',
    method   => 'filter',
    track    => '#foobar',
    on_tweet => sub {
        my $tweet = shift;
        my $twit = Net::Twitter->new(
            username => 'TWITTER_USERNAME',
            password => 'TWITTER_PASSWORD',
        );
        my $time = scalar localtime;
        $twit->update(
            "@$tweet->{user}->{screen_name} "
                . (status_avg() ? 'たぶんいます'
                                : 'たぶんいません')
                . " [$time]"
        );
        warn "posted";
    },
    on_error => sub {
        warn "ERROR: " . join( ', ', @_ );
        $condvar->send;
    },
    on_eof => sub {
        $condvar->send;
    },
);

# Check IRC
my $timer;
my $con = AnyEvent::IRC::Client->new;
$con->reg_cb(
    connect => sub {
        my ( $con ) = @_;
        warn "connected";
    },  
    registered => sub {
        my ( $con ) = @_;
        warn "registered";
        $con->enable_ping( 60 );
    },
    publicmsg => sub {
        my ( $con, $chan, $msg ) = @_;
        if ( $chan eq '#FOOCHANNEL' and
                $msg->{ command } eq 'PRIVMSG' ) {
            my $line = $msg->{ params }->[ 1 ];
            if ( $line =~ /いる[??]/ ) {
                $con->send_chan(
                    $chan, 'NOTICE',
                    $chan, status_avg() ? 'たぶんいます'
                                        : 'たぶんいません',
                );
            } 
        }
    },
);
$con->send_srv( 'JOIN', '#FOOCHANNEL' );
$con->connect( 'irc.example.com', '6667', {
    nick => 'NICKNAME',
    user => 'USERNAME',
    real => 'REALNAME',
} );

# Run tcp server on port 7791.
tcp_server undef, 7791, sub {
    my ($fh, $host, $port) = @_;
    my $poller; $poller = AnyEvent->io(
        fh   => $fh,
        poll => 'r',
        cb   => sub {
            my $r = sysread $fh, my $buf, 1024;
            if ( $r == 0 ) {
                undef $poller;
                return;
            } 
            $i = 0 if $i > 30; # Max status is 30.
            $statuses[$i] = $buf > 0 ? STAY : AWAY;
            warn $statuses[$i];
            $i++;
            syswrite $fh, $buf;
        },
    );
};

$condvar->recv;


あとはスケッチをArduinoにアップロードして起動するのと、Perlプログラムを起動したらOKです!

なお、今回のシステムのデータの流れとしては下記のようになっています。

structure

Arduinoから送信されたデータをPerlのプログラムで保持し、Twitterや社内IRCからの問い合わせに対して返答するといった具合です。なお「いる/いない」の精度を上げるために過去30回の「いる/いない」を保持してカウントが多い方を返答するようにしています。この辺は今後調整が必要かもしれません。

で、実際に動作している様子は下記のようになります。これはIRCのログをキャプチャしたもので、「***checker」というのがチェック用ボットが「いる?」という問いに答えてくれています。

buchochecker_irc

終わりに

今回は急ぎ足でArduinoで組まれた簡単なシステムをご紹介しました。しかし、Arduinoのスケッチを書くためのProcessing言語の紹介や、そもそもArduinoでどんなことができるのか、使用したセンサーはどんなものなのか、なぜこのような回路なのかなど詳細が省かれているため、その辺に関しましては次回機会がありましたらご紹介したいと思います。

なお、Arduinoの情報は http://arduino.cc/ やネットで検索すると様々な情報が出てきますのでそちらも参考にしてみてください。