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側のゲーム作ってくれた友人、そのゲームで使用するイラストを書いてくれた友人にも感謝。

社員大会?参加していろいろブレる

社員大会?に参加して、考えがよくわからなくなったというお話。まとめようなんてことはしてないから、とかく読みづらい文章になるだろうな。章立ては適当。あの時こんなふうにモヤモヤしてたな〜と振り返るための日記でつ

オープニングあけからのクイズ大会なの?

どこのテレビ番組だよ!?と内心突っ込む。よく社員で運動会やる企業があると聞くが、そんな感じで社員同士の交流という名の社畜教育/洗脳か?と疑う。コスとか応援団とかなんなの?でも人数が少くなイカ?…配属されたら僕もやるの?

全員で楽器アプリ使ってオーケストラなの?

スマホに親しもう!ということ?

事業部のお話

割と真面目。

ところどころ挟まれる市民の皆様との交流映像

何がしたいんだってばよ?

社長と筆頭株主企業の社長さんのお話

一番聞かないといけない部分だったはずが、寝落ちった。出だしと終わりだけ聞いたと思う。

エンドロール

部活の卒業生を送る会で流すDVDみたいな、ってかまんまそのノリ。〇〇のおかげで〜みたいな映像を定番曲をBGMに見る。自社の活動・サービスが如何に社会に、人に貢献しているか、を示しているが、意地の悪い見方をするとただの自己満足のための映像。ここでかなり分からなくなる。自分が仕事としてやることがどんな意味をもつのかってことが。
このあたりは"会社の事業は何か?"を考えていくマーケティング的な考えをするとある程度分かるのかもしれない。しかし自分がこの企業を選んだ経緯が、いろんなデータ持ってそう。バックエンドやったら面白そう。位のノリだった。だから、事業とは?とか社会貢献なんて本心ではほとんど考えてなかったと思う。ここで映像と自分との間に崖ができたんだろう。
これを考えたとき、トゥギャッターのまとめで、東大生が海自の慰安に行ったときのまとめ(だったかな?)で、海自のトップが新隊員を迎えるときの挨拶で"まず、この国は守る価値のある国だということを分からせる"という意味のことを言っていた、というのを思い出した。
もしかしたら、映像を通してこの会社は働く意味のある会社であることを伝えようとしていたのでは?と連想が働いた。

最後に

会社の人以外のつながりを通して、働くのは生きるための金を稼ぐため。という極論を言ってしまうくらいの理由だと感じている今、社員大会?ではかなり居心地の悪さを感じた。おそらく価値観が色々と自分の中でもぶれているんだろうか?それともずれているのだろうか?ただ単に病んでいるだけかもしれない。
ただ、この訳の分からないもやもやを解消しようとして書いているのではなく、ただ感じたことを書き留めておきたいと思っただけ。

まとめる気もなく、ここで終わり。

Thunderbird、Googleカレンダー連携不具合解消

表題の通り。Thunderbird 17, Lightning 1.9.1, Provider for Google Calendar 0.18で、Googleカレンダーの予定をThunderbirdから表示・追加・編集できなくなってしまった不具合の解消。

同様の事例はそれなりにあるらしく、買気大作戦stsincerityのメモが見つかった。しかしこれを書いている時点では各ソフトウェアは最新バージョンだったため参考にならないなーと悲観的になっていた。ダメ元でLightningとProvider for Google Calendarを無効化・再起動して再度有効化してみるとうまくGoogle Calendarの予定が表示・追加・編集できるようになった
困ったときの再起動・再設定ダイジ

PPP接続

Ubuntuでの新居のネットワーク接続にだいぶ手間取ったので、残しておかないとまたやるときにメンドイ、ということで残しとく。

基本的には

を参考にした。

"PPPoEによるインターネット接続"を参考にするだけでネットへは繋がるが、次回起動する際にパネルにあるネットワーク接続のアプレット(名称が分からない…)が表示されない。これを解決するため、2つ目の"【ubuntu】フレッツスポットに接続"を参考に設定ファイルを修正。
起動後は

sudo pon dsl-provider

を実行するとネットに繋がる(dsl-providerは各自の設定による。/etc/ppp/peers/を参照)。

修論終えて

修論の発表を終えて、一旦振り返ってみようということで書いてみた。(実体験に基づくものなので、当てはまらない方も多いと思います)

修士は時間が足りない

学部の時に修士の先輩に「学士、修士、博士の中で修士が一番忙しい」と聞いていたが、(博士は体験してないけど)ホントそうだなと実感した。少なくとも学部よりずっと忙しいと思う。
修士過程進学後はまず1年の11月まで講義。7月の後半に研究室に配属されたけど、(修論となる)主テーマ研究とは別の副テーマ研究を3月までに終わらせる必要があって、なんとか年内に終わらせてもそこから就活が始まる。
いい感じに就活が5月末で終わったとして、中間審査が9月にある。3ヶ月あるじゃない!と思うが、研究そのものが始めてならまずどのように進めるか、という方針がよく分からない。そのため、自分だけでやろうとすると(ほとんどは)失敗すると思う。(これを解決するには、こーすればいいだろう。と思っていたものがいい結果だせず、代替案がなかなか浮かばないという点で。)
中間審査での発表が終わって、2月頭の修論提出までやっとまとまった時間が取れる。これが何も気にせず研究に打ち込める時間だと思う。
修論を提出したら息つく暇もなく発表に向けて資料制作が始まる。これが終わって、やっと一息つける。が、就職に伴う引越しなどがあればその準備に追われることになる。

学会発表

僕の所属してる研究室では、修士のうちに3本の学会発表を目標としている。まず2年の8月頃に一本、11月頃に一本、翌年3月に一本という具合。
これが意外と難しい。発表の数ヶ月から半年前には論文を提出しないといけないから。これを考えるとこのスケジュールは、就活が終わる頃、中間審査が終わる頃、修論が終わる頃に論文が完成する、ということになる。
僕の場合は1月提出3月発表だったから、就活と並行して研究する必要はなくて楽な方だとは思う。それでも、副テーマ研究が終わってから取り掛かったわけだけど、11月半ばに始めて1月に論文を提出、しかもそれが処女作というのは、間違いなく独りでは難しい。必ず指導教官の指導が必要になる。ここで論文の構成とか、論理展開をしっかり抑えておくとあとが楽になる。就活の時も自分の研究の説明が楽になるし、一石二鳥。
夏の発表は、僕は成果が出せず書けなかったわけだけど、中間審査でいいアドバイスをいただくためにはやっておくべきだと思う。ここで、(これまで研究をせず)こんなことをしたいと思っていて、これからこんな事やろうと思っています!な発表と、こんなことをしたいと思っていて、(これまでこんな事やってきて、)これからこんな事やろうと思っています!な発表ではアドバイス・質疑応答に大きな差があると思う。
冬の発表では、実際に修論のメインの部分に載せる内容について主に時間を使うわけだから、これをやっておくと、分析がまったく終わっていなくて論文執筆と同時並行だorzなんてことにはなかなかならないと思う。実際は分析と執筆は同時並行になると思うが、心理的なプレッシャーがだいぶ違うんじゃないかな?
ちなみに、残業100時間!という話は時折聞いていて、実際どれくらいなんよ?っていうのが気になったから測ってみると、9時から18時を定時として、中間審査一ヶ月~半月前の生活がだいたい残業100時間(25, 26時くらいまでやってる)、年明けから修論提出、発表前がだいたい残業200時間(数日徹夜がそこそこの頻度)ペースだった。修論発表前は冬の発表用の論文執筆も重なったからだと思ってる。夏は論文書けなかったし。

修士は指導教官の指導の下で研究する

これも修士の先輩から聞いたことだけど、まさにそのとおりだと思う。

  • 「研究する」という経験値が少なくて、視野が狭くなりがち
  • 詰まったときの代替案、解決案の引き出しが多い
  • 関連する研究も多く知ってる
  • 結果の解釈と発展のさせ方が豊富

というのが、そのとおりだと思った主な理由。

実際修論書き上げてみて

学部の頃は修士は凄いことやってるんだなーとか純粋に思ってたけど、実際自分でやってみると、案外大したことやってないなーと。もちろん僕がしょぼいだけという可能性も十分、というか、濃厚?
研究は結構しんどい時もあったけど、新しいことやるっていうのが面白かったし、なかなかできない体験できたし、院に来て正解だったかな。

もしまた研究やるなら

今までやった作業の再検証とか、まとめが意外と苦労した(思い出せない的な意味で)。だから、一ヶ月ごとにショートペーパーとしてまとめておく習慣をつけたいかな。

と、いうことで、まとまりも特にないけど終わっておきます。
(注:あくまで個人の振り返りなので、修士全員に当てはまることはありません。特に、残業時間にしてみたところについては、作業量・内容に対して僕の能力の不足が大きな要因となっていると思われます。)

DBから単位時間毎にデータ数を集計し、csv形式で出力

データ概要とやりたいこと

以下のようなテーブルがある

id time(timestamp型) text
----------------- 2012-11-16 16:41:09+09 @minya2sen それじゃあ、適当な時間に呼んでおくれ (((o(*゚▽゚*)o)))
----------------- 2012-11-16 17:05:03+09 ★プロのパーソナルトレーナー監修★アメリカの最新理論を取り入れた2ヶ月でマイナス10キロを目指す!http://t.co/0VLMFARg
----------------- 2012-11-16 16:45:21+09 れっつごー(((o(*゚▽゚*)o)))
----------------- 2012-11-16 16:41:09+09 【生放送】外配信 Inter BEE 2012─ 音と映像と通信のプロフェッショナル展 ─ 国際放送機器展 in 幕張メッセ 9 20分 を開始しました。 http://t.co/YkRuJPeq #lv115557930
----------------- 2012-11-16 16:45:21+09 Flicker秩父第二十五番久昌寺のセットへ、一巡目(2009年冬)に撮った弁天池の凍結と御手判の画像を追加。 http://t.co/N9jm2saR
----------------- 2012-11-16 16:41:09+09 @04x12 あれやばすぎ!
----------------- 2012-11-16 16:45:21+09 バトリオでキラやレアが出て喜んでるガキ相手に「頼む!譲ってくれ!」なんて言うなよ
----------------- 2012-11-16 16:53:19+09 RT @yuzi07090710: おれ、罰ゲームを考えるのが昔から得意だった。大学のとき、コンビニに入ってストッキングをレジに出して『試着できますか?』って聞く罰を考えた。3回やって全部おれが負けた。
----------------- 2012-11-16 16:49:25+09 RT @kinitwi: 見て、この男と女の違い。『女は一ヶ月で四回性格が変わる』ホルモン的にこんなに違うんだって男には理解してほしい yamadaalice_bot http://t.co/wfayxsJe
----------------- 2012-11-16 17:05:03+09 RT @soraotto: ムービック商品情報:K クッション 3,150円 @movic_jp http://t.co/KrKQqD1N あっあたまおかしい
----------------- 2012-11-16 16:57:14+09 RT @niftynews: ジェットスターに厳重注意へ http://t.co/h2UsvVTx #niftynews

これを、↓のような形(時間:分 | データ数)で集計したい。(今回の例は同日のデータしか扱ってないので特に問題はないが、実際には平均を出す必要があると思うが、今回の備忘録では対象外。)

time count
00:00 6591
00:10 5509
00:20 5654
00:30 5200
00:40 4021
00:50 1638
01:00 1030
01:10 268

集計

↓のSQLで目的のデータ整形はできる。

WITH tmp AS ( 
    SELECT id AS id, extract('hour' from time) || ':' || trunc(extract('minute' from time) / 10 ) * 10 AS time \
    FROM table_name
    )
SELECT time::time, count(id) FROM tmp GROUP BY time ORDER BY time;

少し補足をすると、extractでtimeから時間と分の値を取得。今回は10分刻みで集計したかったから、分を10で割った商をまた10倍することで目的のデータのtimeを作ってる(truncは切り捨て)。
これらで得た値を || で連結してる。ここで出力されるデータ型は文字列なので、次にSELECTするときにはtime型で解釈するよう time::timeとしている。
データ数の集計は、idの数を調べれば良い(重複がないという条件で)と思うので、idの数を調べる。

ファイルに出力

csv形式で出力する。↓のSQLで実現。

COPY (
    WITH tmp AS (
        SELECT id AS id, extract('hour' from time) || ':' || trunc(extract('minute' from time) / 10 ) * 10 AS time \
        FROM table_name
        )
    SELECT time::time, count(id) FROM tmp GROUP BY time ORDER BY time
    )
TO '/tmp/record_table.csv' CSV;

SQLの自下げってどれくらいが読みやすいんだろうなー?

lsaパッケージインストール

lsaパッケージをインストする際のJAVA設定の備忘録
R version 2.14.1

install.packages("lsa")

を実行すると下記のようなエラーが出た。

Make sure you have Java Development Kit installed and correctly registered in R.
If in doubt, re-run "R CMD javareconf" as root.
  
ERROR: configuration failed for package ‘rJava’
* removing ‘/home/yamaguchi/R/i686-pc-linux-gnu-library/2.14/rJava’
ERROR: dependency ‘rJava’ is not available for package ‘RWekajars’
* removing ‘/home/yamaguchi/R/i686-pc-linux-gnu-library/2.14/RWekajars’
ERROR: dependencies ‘RWekajars’, ‘rJava’ are not available for package ‘RWeka’
* removing ‘/home/yamaguchi/R/i686-pc-linux-gnu-library/2.14/RWeka’
ERROR: dependencies ‘RWeka’, ‘rJava’ are not available for package ‘Snowball’
* removing ‘/home/yamaguchi/R/i686-pc-linux-gnu-library/2.14/Snowball’
ERROR: dependencies ‘Snowball’, ‘RWeka’ are not available for package ‘lsa’
* removing ‘/home/yamaguchi/R/i686-pc-linux-gnu-library/2.14/lsa’
  
 ダウンロードされたパッケージは、以下にあります
        ‘/tmp/Rtmp1qDjRx/downloaded_packages’
 警告メッセージ:
1: In install.packages("lsa") :
   パッケージ '‘rJava’' のインストールは、ゼロでない終了値をもちました
2: In install.packages("lsa") :
   パッケージ '‘RWekajars’' のインストールは、ゼロでない終了値をもちました
3: In install.packages("lsa") :
   パッケージ '‘RWeka’' のインストールは、ゼロでない終了値をもちました
4: In install.packages("lsa") :
   パッケージ '‘Snowball’' のインストールは、ゼロでない終了値をもちました
5: In install.packages("lsa") :
   パッケージ '‘lsa’' のインストールは、ゼロでない終了値をもちました

関係するのか分からないが、

sudo update-alternatives --config java

でopenJDKからSUN JDK6に変更して、

sudo R CMD javareconf

を実行して、再度

install.packages('lsa', dependencies=T)

でインストール成功。