NikaLog

プログラムと音楽と、時々デザイン。

Zeemoteを使ったAndroidのプログラムを組んでみました

Google Developer Day 2011にてZeemoteというデバイスを配っていました。ZeemoteというのはAndroid用のbluetoothのコントローラで、動かした様子は下のような感じ。

GAPSIS.JP様の映像を引用…

GDD2011から1ヶ月くらい放置していたのですが、ちょっと時間ができたので、少しいじって見ました。

とりあえず触ってみる

なにはともあれ、ZeemoteとAndroidをペアリングしなければ動かすことはできません。ペアリングは、[設定]-[無線とネットワーク]-[Bluetooth]にチェックを入れて、さらにその下の[Bluetoothの設定]から出来ます。

f:id:croneco:20111210020320j:image

Zeemoteの電源を入れて、付近のデバイスを検索します。

f:id:croneco:20111210020321j:image

見つかると、Zeemote JS1 Hというのがリストに表示されます。

f:id:croneco:20111210020322j:image

Zeemote JS1 Hを押して、PIN「0000」を入力します。

f:id:croneco:20111210020323j:image

そうすると、ペアリングされたデバイスにZeemote JS1 Hが表示されます。

f:id:croneco:20111210020324j:image

Zeemoteのページを見ると、R-TYPEというゲームでZeemoteが使えるみたいなので、早速ダウンロードしてプレイすると…、おお、確かにZeemoteで動く。

SDKのダウンロード

http://www.zeemote.com/にアクセスすると、右上に「DOWNLOAD ZDK」というリンクがあります。押してみましょう。

f:id:croneco:20111210013928j:image

すると、開発者ページが現れます。SDKをダウンロードするためには開発者登録が必要なので、必要事項を記入して、登録をしてください。

登録ができたら、開発者ページからログインをすることで、SDKダウンロードページに行くことができます。

Androidで開発をしようと思うので、Zeemote Android SDK 1.6.0をダウンロードします。

f:id:croneco:20111210013929j:image

ダウンロードしたzipファイルを展開するとこんな感じ。

f:id:croneco:20111210013930j:image

早速、Eclipseを起動して、examplesの下のandroid-sampleをインポートしてみます。

f:id:croneco:20111210013931j:image

そして、実行。Zeemoteのボタンやスティックを動かしていると、なんか表示されています。

f:id:croneco:20111209181544j:image

表示はこんな感じ。スティックの傾きや、ボタンのPress、Releaseが表示されています。

f:id:croneco:20111210021010j:image

オリジナルのプログラムを作成する

サンプルのソースを見てみると、案外難しくなさそうに見えます。buttonPressed関数はボタンが押されたときに呼ばれ、buttonReleased関数はボタンが離されたときに呼ばれます。2つの関数はButtonEventを引数に取りますが、ButtonEventにボタンの情報が格納されているのでしょう。joystickMoved関数も名前そのままで、スティックを動かしたときに呼ばれます。他にもconnected/disconnected関数があり、これらの関数は、IStatusListener, IJoystickListener, IButtonListenerを実装(Implements)する必要があるため、ソースコードに記述されています。

1からプログラムを作るときは、SDKに含まれているAndroid Programmer's Guideの5ページ "How To Use"に沿えば大丈夫だと思います。

まず、SDKのlibsフォルダをプロジェクトにコピーして、ビルドパスを通します。

また、bluetoothを使用するため、 android.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMIN の許可をAndroidManifest.xmlに書き込みます。

f:id:croneco:20111210022000j:image

そして、Zeemoteとの接続に必要なUIはライブラリが提供するものを使用したいため、com.zeemote.zc.ui.android.ControllerAndroidUi$Activity というアクティビティをアプリケーションから呼び出す設定をAndroidManifest.xmlに書き込みます。

f:id:croneco:20111210022301j:image

ソースコードはサンプルからコピペしてきて、必要な部分を編集するといいと思います。私はmp3ファイルを4つロードして、ボタンやスティックが操作されたら、アサインしたmp3を再生するというプログラムを作ってみました。

mp3の再生の部分を手を抜いているため、サウンドに重ねてサウンドを再生することができていませんが、一応音が鳴っているので、Zeemoteでの操作を受け取っていることが分かります。あとは、どんどん改造していけばいいですね。

下記に実際に作ったソースを掲載します。あまり良いコードではないのですが、参考になれば幸いです。

package hoge.zeemote.test2;

import java.io.IOException;

import com.zeemote.zc.Controller;
import com.zeemote.zc.event.BatteryEvent;
import com.zeemote.zc.event.ButtonEvent;
import com.zeemote.zc.event.ControllerEvent;
import com.zeemote.zc.event.DisconnectEvent;
import com.zeemote.zc.event.IButtonListener;
import com.zeemote.zc.event.IJoystickListener;
import com.zeemote.zc.event.IStatusListener;
import com.zeemote.zc.event.JoystickEvent;
import com.zeemote.zc.ui.android.ControllerAndroidUi;

import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class Zeemote_test2Activity extends Activity implements IStatusListener,
IJoystickListener, IButtonListener {

  private static final int MENU_CONTROLLER = 1;
  private Controller controller;
  private ControllerAndroidUi controllerUi;
  private boolean controllerUiShown;
  public static final int GUIUPDATEIDENTIFIER = 0x101;
  private String info;

  MediaPlayer _hh;
  MediaPlayer _bd;
  MediaPlayer _sd;
  MediaPlayer _mac;

  TextView label;

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

    controller = new Controller(1);
    controller.addStatusListener(this);
    controller.addButtonListener(this);
    controller.addJoystickListener(this);

    controllerUi = new ControllerAndroidUi(this, controller);
    controllerUiShown = false;

    _hh = MediaPlayer.create(this, R.raw.hh);
    _bd = MediaPlayer.create(this, R.raw.bd);
    _sd = MediaPlayer.create(this, R.raw.sd);
    _mac = MediaPlayer.create(this, R.raw.mac);

    label = (TextView)findViewById(R.id.lblText);
    label.setTextSize( (float) 120.0);
    }

  @Override
  public void onResume() {
    super.onResume();
    if (!controllerUiShown) {
      // show the controller activity.
      controllerUi.startConnectionProcess();
      controllerUiShown = true;
    } else {
      // the controller activity was hidden.
      controllerUiShown = false;
    }

    try {
      _hh.prepare();
      _bd.prepare();
      _sd.prepare();
      _mac.prepare();
    } catch (IllegalStateException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    } catch (IOException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    if (!controllerUiShown) {
      // If the controller activity is not shown,
      // disconnect the Zeemote controller.
      try {
        controller.disconnect();
      } catch (Exception e) {
      }
    }
  }

  /**
   * Invoked during init to give the Activity a chance to set up its Menu.
   *
   * @param menu
   *            the Menu to which entries may be added
   * @return true
   */
  @Override
  public boolean onCreateOptionsMenu(final Menu menu) {
    super.onCreateOptionsMenu(menu);

    menu.add(0, MENU_CONTROLLER, 0, "Controller");
    return true;
  }

  /**
   *
   */
  @Override
  public boolean onPrepareOptionsMenu(final Menu menu) {
    super.onPrepareOptionsMenu(menu);
    return true;
  }

  /**
   * Invoked when the user selects an item from the Menu.
   *
   * @param item
   *            the Menu entry which was selected
   * @return true if the Menu item was legit (and we consumed it), false
   *         otherwise
   */
  @Override
  public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
    case MENU_CONTROLLER:
      controllerUi.showControllerMenu();
      controllerUiShown = true;
      return true;
    }
    return false;
  }

  public void buttonPressed(ButtonEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ
    // button id 0:A(bd), 1:B(sd), 2:C, 3:power
    int b = arg0.getButtonID();

    if(b==0){
      _bd.start();
      traceLog("BD");
    } else if(b==1){
      _sd.start();
      traceLog("SD");
    }

  }

  public void buttonReleased(ButtonEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void joystickMoved(JoystickEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ
    int x = arg0.getScaledX(-100, 100);
    int y = arg0.getScaledY(-100, 100);

    if(x < -50){
      _hh.start();
      traceLog("HH");
    }

    if(x > 50){
      _mac.start();
      traceLog("MAC");
    }
  }


  public void batteryUpdate(BatteryEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void connected(ControllerEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  public void disconnected(DisconnectEvent arg0) {
    // TODO 自動生成されたメソッド・スタブ

  }

  protected void traceLog(final String s) {
    info = s;
    Message m = new Message();
    m.what = GUIUPDATEIDENTIFIER;
    this.ViewUpdateHandler.sendMessage(m);
  }

  Handler ViewUpdateHandler = new Handler() {

    public void handleMessage(Message msg) {
      switch (msg.what) {
      case GUIUPDATEIDENTIFIER:
        label.setText(info);
        break;
      }
      super.handleMessage(msg);
    }
  };
}