Tuesday, March 10, 2015

RICHO THETAの簡単なAndroidアプリ

RICHO THETAを最初見たときは,あんまりピンと来なかったのですが使ってみると便利で面白いですね.
そんなTHETAをいじる機会があったので,簡単なAndroid用のプログラムを作ってみました.

SDKは下記のサイトからDLできます.

RICHO THETA Developpers
https://developers.theta360.com/ja/


SDKに付属しているサンプルプログラムは私にとって複雑で,何をしているのかわかりにくいものでした.
そこで,SDKのサンプルとリファレンスを頼りに,以下のようなシャッターを切るだけの簡単なサンプルを作りました.

SimpleTHETA_01

このサンプルはCONNECT Buttonを押してTHETAに接続し,SHOOT Buttonを押してシャッターを切るというプログラムです.
切断するときはDISCONNECT Buttonを押します.
THETAのステイタスからGUIを動かすサンプルとして,各操作の状況をTHETA statusというtextviewに描画しています.

それではこのプログラムの説明を行います.

まずは,以下のように,インターネットのpermissionを取ります.
この記述は,AndroidManifest.xmlに記述します.
<uses-permission android:name="android.permission.INTERNET"/>
つぎに,GUIのレイアウトを設定します.デフォルトではactivity_main.xmlに記述します.
    <LinearLayout
    android:id="@+id/linearLayout1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="28dp"
    android:layout_marginTop="64dp"
    android:orientation="vertical" >
        
        <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="THETA status:" />    
             
         <TextView
         android:id="@+id/status"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:text="Status" />
        
        </LinearLayout>
        
        <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <Button
         android:id="@+id/connect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="CONNECT" />
         
         <Button
         android:id="@+id/disconnect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="DISCONNECT" />
         
         <Button
         android:id="@+id/shoot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="SHOOT" />
        
        </LinearLayout>
 
    </LinearLayout>

これまでで,準備が整ったのでMainActivity.javaに処理を記述します.
この処理は基本的に各ボタンのListnerを設定して,そのボタンに合わせて処理を行います.
気を付ける点は以下の2つです.
  1. THETAとのやりとりはINTERNETを使うので,AsyncTaskなどを使って別スレッドで行う.
  2. GUIを別スレッドから操作するときは,Handlerを使う.
この点だけ気を付ければ特別難しい処理はないと思います.
各ボタンを押すとConnectTask,DisConnectTask,ShootTaskが実行されます.

ConnectTaskでは,指定したIPアドレスのTHETAに接続を行い,成功したらCONNECTED,失敗したらERRORと表示します.
DisConnectTaskでは,TETAとの接続を断ち,成功したらDISCONNECTED,失敗したらエラーログを表示します.
ShootTaskでは,シャッターを切り,成功したらSHOOT,失敗したら各エラーを表示します.

public class MainActivity extends Activity {
 
 PtpipInitiator camera;
 private static final String IP_ADDRESS = "192.168.1.1";
 private TextView txt_theta_status;
 private Button btn_theta_connect;
 private Button btn_theta_disconnect;
 private Button btn_theta_shoot;
 
 private Handler handler = null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  txt_theta_status = (TextView) findViewById(R.id.status);
  btn_theta_connect = (Button) findViewById(R.id.connect);
  btn_theta_disconnect = (Button) findViewById(R.id.disconnect);
  btn_theta_shoot = (Button) findViewById(R.id.shoot);
        
  btn_theta_connect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ConnectTask().execute();
       }
  });
        
         btn_theta_disconnect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new DisConnectTask().execute();
       }
         });
        
         btn_theta_shoot.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ShootTask().execute();
   }
         });
        
         handler = new Handler();
 }
 
 
 private class ConnectTask extends AsyncTask {
     @Override
     protected String doInBackground(String... params) {
         try {
             camera = new PtpipInitiator(IP_ADDRESS);
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("CONNECTED");
              }
             });
             
             
         } catch (Throwable e) {
             e.printStackTrace();
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("ERROR");
              }
             });
         }
         return null;
     }
 }
 
 private class DisConnectTask extends AsyncTask {
  @Override
  protected Boolean doInBackground(Void... params) {

   try {
    PtpipInitiator.close();
    
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("DISCONNECTED");
              }
             });
    
    return true;

   } catch (Throwable throwable) {
    final String errorLog = Log.getStackTraceString(throwable);
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText(errorLog);
              }
             });

    
    return false;
   }
  }
 }

 private static enum ShootResult {
  SUCCESS, FAIL_CAMERA_DISCONNECTED, FAIL_STORE_FULL, FAIL_DEVICE_BUSY
 }
 
 private class ShootTask extends AsyncTask {

  @Override
  protected ShootResult doInBackground(Void... params) {
   try {
    PtpipInitiator camera = new PtpipInitiator(IP_ADDRESS);
    camera.initiateCapture();
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("SHOOT");
              }
             });
    return ShootResult.SUCCESS;

   } catch (IOException e) {
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
              }
             });
    return ShootResult.FAIL_CAMERA_DISCONNECTED;
   } catch (ThetaException e) {
    if (Response.RESPONSE_CODE_STORE_FULL == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_STORE_FULL");
               }
              });
     return ShootResult.FAIL_STORE_FULL;
    } else if (Response.RESPONSE_CODE_DEVICE_BUSY == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_DEVICE_BUSY");
               }
              });
     return ShootResult.FAIL_DEVICE_BUSY;
    } else {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
               }
              });
     return ShootResult.FAIL_CAMERA_DISCONNECTED;
    }
   }
  }

 }

以上で,AndroidアプリからTHETAのシャッターを切る方法の説明を終わります.

Tuesday, March 3, 2015

Raspberry Pi 2のケースを自作する

Raspberry Pi 2を手に入れたのでケースを作ってみました.
市販のケースを買うのもいいですが,ケースを自作するのも楽しさの一つだと思います.

調べてみるとケースは3Dプリンタやレーザーカッターで作っるのがメジャーなようです.
3Dプリンタで作ると市販の射出成形のケースに見劣りしそうだったので,レーザーカッターをメインに使いました.
レーザーカッターは基本的に板を切るものなので,積層や箱組で立体的にケースを作るという手法が多く見られました.
今回,私は板を金属の支柱で支えることでケースを作ることにしました.
シンプルですが,各コネクタやIOへのアクセスを考えると悪くない選択だと思います.

Raspberry Pi2とケースの部品は以下のようになります.
各部品は,自作品や在庫品を使用しています.
ここを見てくれた方の参考になるよう,同等の市販品を参考に載せました.
もっと安いものもありますが,趣味に使うものなので良い物を選んでいます.

RaspberryPi2 Case veiw4

天板と下板のデータはここからDLできます.
DXF,ISG,STLで用意したので興味のある方は是非使ってみてください.
下板のパーツのRaspberry Pi2固定用の穴はM2.6のタップでメネジをたてて下さい.
それができない場合は,φ2.6の穴に拡張して,ナットで固定してください.
その場合は,ナットの厚み分,プラネジを長くしてください.
また,下板-天板接続用ネジの下面側のネジはナットよりも背の高いものを使用してください.

Drawing of Raspberry Pi2 Case


それでは,これらの部品を組み立てていきます.
まずは,下板にプラスティックの支柱とM2.6のプラネジを使って固定します.
さらに,アルミ製の支柱を下板に止めます.

RaspberryPi2 Case veiw2

最後に,天板を固定すれば完成です.
天板には,お決まりのロゴを彫刻しました.
すりガラス風のアクリルを使っているので,ロゴがはっきりと見えます.
すりガラス風のロゴを彫刻する場合は, すりガラスのような加工が入った面に彫刻するほうがきれいにロゴが見えると思います.

RaspberryPi2 Case veiw1

横から見ると以下のようになります.
各コネクタやIOは隠していないのでちゃんとアクセスできます.

RaspberryPi2 Case veiw3

簡単な作りですが,それなりに見栄えのするものになったと思います.