arduino、Javaドシロウトが書いてみた、はじめてのarduino

とにかくコードが見たいって人は読みとばすと良いよ。ちなみに、Javaarduinoもズブの初心者なので色々突っ込みどころ満載だよ^^

概要

先日大学院の友人たちと一緒にハッカソン(一日から二日くらい?の短期間で何かアプリケーションのプロトタイプ作ろうぜ!なイベント)に参加した。そこで作ることになったのはティッシュ使用量削減を目的としたアプリ、すなわちSTS(Social Tissue Service) × 育成ゲーム。
arduinoというハードウェアとセンサを用いてデータを取得し活用するという、オフラインとオンラインの連携を目指したアプリとなっている。新卒×学生ハッカソン - スマホアプリを作ろう -の趣旨であるスマホアプリから逸脱した感は否めないが、まあハッカソンなんで作りたいもん作ろうやソフトウェアのみならずハードウェアまで組み込んだ、枠にとらわれない試みといえるだろう。

システム

概要

本システムはティッシュ使用(ティッシュがシュッと引かれること)をセンサーにより感知し、DBのティッシュ使用回数をカウントアップする感知部及び取得したティッシュ使用回数を利用した育成ゲームであるSTS部に別れる。感知部はarduinoおよび曲げセンサーにより構成され、センサーからの出力をEclipse上で実行しているJavaによりキャッチし、DBの更新をおこなっている。また同時に、現在何枚使用したかを音声で通知する。STS部はLAMPにより実装されている(こっちはタッチしてないので詳しくは知らない)。

感知部詳細

ティッシュの使用を曲げセンサーを用いて取得する。曲げセンサーは抵抗となっており、arduino側で電圧の変化でセンサーの曲がり具合を判定する。ティッシュ使用時にarduinoが出力をおこなうようになっており、Javaでこれを取得し、DBへ接続、SQLを実行している。

コード

環境構築

開発環境はWindows7 64bit、JDK7、Eclipse4.2
Javaarduinoとシリアルポート通信をおこなうにあたり、メモメモ... MacのJavaでAVRとシリアル通信したいを参考に環境構築をおこなった。
手順としては、rxtx-2.1-7-bins-r2.zipをダウンロード、解凍したディレクトリの中にあるRXTXcomm.jarをC:\Program Files (x86)\Java\jre7\lib\extに、Windows\i368-mingw32にあるrxtxParallel.dll、rxtxSerial.dllをC:\Program Files (x86)\Java\jre7\binにコピーした。(32、64bit版のJavaの両方にコピーしてしまったから、うまく動かなかったら追加してみるといいんじゃないかな?)
パスが見つからないとか言われたら、新しく実行環境の構築をすれば良いと思うよb

ディレクトリ構成(関係する部分のみ抜粋)
hackathon
  `-- src
       |- デフォルトパッケージ
       |   |- DBHandler.java // DB関連
       |   |- MusicPlay.java // 音声ファイル再生
       |   `- main.java // メインの実行ファイル
       `- wav // 音声ファイル置き場
           |- 1.wav
            ....
           `- 20.wav
arduino

sketch_aug25a.ino

// このコードは友人が書いたもので、理解できてない部分も多々あります。
// serial_voltage_byte_sample1
int mage_pin = 2;
int mage_value = 0;
int val = 0;
int seri = 0;
void setup() {
  Serial.begin( 9600 ); // この番号は後で使う
}
void loop() {
  mage_value = analogRead( mage_pin );
  // 曲げセンサーを通して検知した値が500 ~ 650だったので、0 ~ 100にスケールを合わせてみた
  val = map(mage_value, 500, 650, 0, 100); 
  if(val >= 40){ // 曲がってると判断
    seri = 1;
  }else if(val < 40){ // 曲がってないと判断
    seri = 0;
  }
  if (seri > 0) { // 曲がっている場合のみ出力
    Serial.print(seri);
  }
  delay( 300 ); // 0.3secごとにセンサーのデータを取得する
}
Java

main.java(実行するメインのファイル)

import java.io.*;
import gnu.io.*;
 
public class main {
	public static final void main(String[] args){
		  try{
                   // arduino IDE -> ツール -> シリアルポートで確認
		   CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier( "COM3" );
		    // Linuxならこんな感じらしいぉ
                   //CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier("/dev/tty.usbserial-A400FMPJ");		  
                   // 第一引数はアプリケーション名: sketch_aug25aはスケッチのファイル名
		   SerialPort port = (SerialPort)portID.open("sketch_aug25a", 5000); // 接続に失敗したら5sec待って再度トライ
		   
		   // 第一引数のポートはスケッチファイルで指定した数字
		   port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
		   port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
		    
		   InputStream is = port.getInputStream();
		    
		   int c; // arduinoからの出力読み込み用
		   boolean update_flag = false; // DB updateフラグ
		   boolean flag = true; // 無限ループ用フラグ
		   System.out.println("start reading");
		   int count=0; // 音声ファイル用カウンタ
		   DBHandler dbh = new DBHandler(); // DBへの接続、SQL実行クラス
		   PlayMusic pm = new PlayMusic(); // 音声ファイル再生クラス
		   while(true){
                      c = is.read();
		      if (c != -1) {
		    		  System.out.println(count+ "->" + c); // デバッグ用
		    		  // arduinoでは1を出力するはずが、なぜか48 or 49になってる
  		    		  if (c > 48) { // ティッシュ抜き取り
		     			  update_flag = true;
 		    		  } else {           
		     			  update_flag = false;
		    		  }
		    		  if (update_flag) {
                      count++;
	    				  pm.filename = "wav\\" + count + ".wav";
	    				  pm.play(); // 音声ファイル再生
	    				  dbh.update(); // update文実行
                                          update_flag = false;
		                  }
		      }
		      if (flag == false) {
		    	  break;
		      }
		   }
                   is.close();
                   port.close();
                   System.out.println("finished reading");
		  } catch(NoSuchPortException e){
                      System.err.println("Can Not Find Device");
                      e.printStackTrace();
		  } catch(PortInUseException e){
                      System.err.println("Can Not Open Device");
                      e.printStackTrace();
		  } catch(UnsupportedCommOperationException e){
                      System.err.println("Invalid Parameter");
                      e.printStackTrace();
		  } catch(IOException e){
                      e.printStackTrace();
		  } catch(Exception e){
                      e.printStackTrace();
		  }
	 }
}

DBHandler.java

import java.sql.*;
public class DBHandler {
	String host = "SERVER_URL:DB_PORT"; // MySQLを使用したので、ポートはデフォルトの3306
	String table = "hackathon";
	String url = "jdbc:mysql://" + host + "/" + table + "?useUnicode=true&characterEncoding=utf8";
	String user = "hohe";
	String password = "huge";

	public void select() {
                // DBに保存されたデータ確認用。
		try {
			Class.forName("org.gjt.mm.mysql.Driver");
			Connection con = DriverManager.getConnection(url, user, password);
			System.out.println("接続成功");
			String sql = "SELECT * FROM user u JOIN tissue t ON u.id = t.user_id";
			Statement statement = con.createStatement();
			ResultSet reslut = statement.executeQuery(sql);

			// SELECT結果の表示
			while(reslut.next()){
        			int id = reslut.getInt("id");
        			String name = reslut.getString("name");
    	        		int current_count = reslut.getInt("current_count");
    		        	System.out.print(id + "\t");
    			        System.out.print(name + "\t");
    		        	System.out.println(current_count + "\n");
    		        }
    		        con.close();
		} catch(Exception e){
    		        System.out.println("例外発生:"+ e);
         	}
	}
	public void update () {
		// tissueテーブルのcurrent_countをカウントアップ
		try {
			Class.forName("org.gjt.mm.mysql.Driver");
			Connection con = DriverManager.getConnection(url, user, password);
			System.out.println("接続成功");
                        // ホントはユーザを決め打ちにしたくないけど、プロトタイプということで…
			String sql = "UPDATE tissue SET current_count = current_count + 1 " +
					"WHERE user_id = (SELECT id FROM user WHERE name = 'hoge')";
			Statement statement = con.createStatement();
			int reslut = statement.executeUpdate(sql); // UPDATE実行結果取得(使わないけど)
			System.out.println("executed");
    		        con.close();
		} catch(Exception e){
    		        System.out.println("例外発生:"+ e);
    	        }
	}
}

PlayMusic.java

import java.io.File;
import java.io.IOException;
import javax.sound.sampled.*;

public class PlayMusic {
	String filename; // 再生するwavファイル
	int count = 0; // ループする回数
	int sleep_time = 0; // 再生後待機する時間(sec) not use
	public void play() {
		System.out.println("start play");
		Clip clip = null;
                // このファイルが置かれているパスからの相対パスだとうまく読み込めなかった。ので、絶対パスで指定している。
                // プロジェクトルートからの相対パスなら良いのかな?
                // Winの場合、C:hoge\\fuga\\ となる。 バックスラッシュをエスケープする必要が有ることに注意
		String wav_file = "ABSOLUTE_PATH" + filename;
		System.out.println(wav_file);
		AudioInputStream audioInputStream;
		try {
                	File soundFile = new File(wav_file);
                        audioInputStream = AudioSystem.getAudioInputStream(soundFile);
                        AudioFormat audioFormat = audioInputStream.getFormat();
                        DataLine.Info info = new DataLine.Info(Clip.class, audioFormat);
                        clip = (Clip)AudioSystem.getLine(info);
                        clip.open(audioInputStream);
                        clip.loop(count); // countの回数分だけループ(今回は使わないので0を指定。コメントアウトしたらどうなるかは試してない)
                } catch (UnsupportedAudioFileException e) {
                 	e.printStackTrace();
                } catch (IOException e) {
        	        e.printStackTrace();
                } catch (LineUnavailableException e) {
        	        e.printStackTrace();
                }
        // 1秒経過したら終了する
        // これを書かないと、音声ファイルが再生されない。
        // 実際に使用するときは何らかの方法で再生時間を取得しないといけないのかな?
        try {
        	Thread.sleep(1000);
        } catch (InterruptedException e) { // 何もしない
        }
        clip.stop();
        System.out.println("finished playing");
        //System.exit(0); // これを実行すると処理が終了する(再生してプログラム終了)
	}
}

最後に

Javaarduinoもド素人なのに、ネット上のソース繋げるだけで意外とできるもんやな〜と感心しましたよ。動くとやっぱり楽しいし、arduinoも使えると今後の幅が広がる?かもしれないね^^

もちろん、arduino側のコード書いてくれた友人、STS側のゲーム作ってくれた友人、そのゲームで使用するイラストを書いてくれた友人にも感謝。