2009/09/23

Java on OSXでWiiリモコン

今さら感は拭えないが、Wiiリモコンとarduinoを組み合わせて遊びたい。
そこでまずは、MacBook上でJavaからWiiリモコンを触れるようにした。
wii remote

準備

JavaとWiiリモコンをBluetoothでつなげてみよう - ブログ: 岡崎 - Okazaki's blog」や「WiiLi.org Wii Linux - JP:WiiremoteJ/ReadMe」などにあるように、必要なものは、Java版Bluetooth API(JSR-82)のオープンソース実装bluecoveと、WiiプロトコルのJavaライブラリWiiRemoteJだけ。2009年9月22日現在での最新バージョン、BlueCove2.1.0とWiiRemoteJ v1.6を使ってみた。

JavaVMの起動オプション

いきなりつまずいた。リモコンを探しに行くところで、
java.lang.IllegalArgumentException: PCM values restricted by JAR82 to minimum 4097
とエラーメッセージが出て止まってしまう。
Googleで検索してみると色々勉強になる(例えばここ)が、結局、「JavaVMの起動オプションに以下を加えることで問題を回避できる」そうだ。
-Dbluecove.jsr82.psm_minimum_off=true
理由は問わない。魔法だと思おう...

実装例 for 安定動作?

とりあえずWiiRemoteJの配布物に含まれているサンプルコードを動かしてみる。
リモコン探して、つないで、ボタンのイベントや加速度センサのデータなどを取得する、一通りの方法はとりあえず分かった。が、このWiiRemoteJ、安定して動作させるのがなかなか難しい、ということも分かった。特にリモコンとの接続確立時に、アプリ側で対処できない(と思われる)エラーがよく起きる。

色々やってみた結果、サンプルコードで使われているfindRemote()でなく、findRemotes(WiiDeviceDiscoveryListener listener)を使った方が、動作が安定する、っぽい。あくまで「っぽい」だけど。WiiDeviceDiscoveryListenerを使ったコードは、例えば以下のようになる。
import wiiremotej.WiiRemote;
import wiiremotej.WiiRemoteJ;
import wiiremotej.event.WRAccelerationEvent;
import wiiremotej.event.WRButtonEvent;
import wiiremotej.event.WRStatusEvent;
import wiiremotej.event.WiiDeviceDiscoveredEvent;
import wiiremotej.event.WiiDeviceDiscoveryListener;
import wiiremotej.event.WiiRemoteAdapter;

public class TestWiiRemote extends WiiRemoteAdapter implements WiiDeviceDiscoveryListener
{
public static void main(String[] args)
{
WiiRemoteJ.setConsoleLoggingAll();
TestWiiRemote test = new TestWiiRemote();
try{
WiiRemoteJ.findRemotes(test,1);
}catch(Exception e){
e.printStackTrace();
}
}

public TestWiiRemote()
{
}

public void findFinished(int numFound)
{
System.out.println(numFound+" remotes has been found!");
}

public void wiiDeviceDiscovered(WiiDeviceDiscoveredEvent evt)
{
System.out.println("remote(#"+evt.getNumber()+") was discovered!");
WiiRemote remote = (WiiRemote)evt.getWiiDevice();

try{
remote.setAccelerometerEnabled(true);
remote.setSpeakerEnabled(true);
remote.setLEDIlluminated(0,true);
}catch(Exception e){
e.printStackTrace();
if(remote!=null && remote.isConnected()){
remote.disconnect();
}
}

final WiiRemote remoteF = remote;
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable(){
public void run(){
if( remoteF!=null && remoteF.isConnected() ){
remoteF.disconnect();
}
}
}));

remote.addWiiRemoteListener(this);
}

public void disconnected()
{
System.out.println("Remote disconnected...");
System.exit(0);
}

public void statusReported(WRStatusEvent evt)
{
System.out.println("Battery level: " + (double)evt.getBatteryLevel()/2+ "%");
System.out.println("Continuous: " + evt.isContinuousEnabled());
System.out.println("Remote continuous: " + evt.getSource().isContinuousEnabled());
}

public void accelerationInputReceived(WRAccelerationEvent evt)
{
double x = evt.getXAcceleration();
double y = evt.getYAcceleration();
double z = evt.getZAcceleration();

System.out.println(x+","+y+","+z);
}

public void buttonInputReceived(WRButtonEvent evt)
{
if (evt.wasPressed(WRButtonEvent.TWO))System.out.println("2");
if (evt.wasPressed(WRButtonEvent.ONE))System.out.println("1");
if (evt.wasPressed(WRButtonEvent.B))System.out.println("B");
if (evt.wasPressed(WRButtonEvent.A))System.out.println("A");
if (evt.wasPressed(WRButtonEvent.MINUS))System.out.println("Minus");
if (evt.wasPressed(WRButtonEvent.HOME))System.out.println("Home");
if (evt.wasPressed(WRButtonEvent.LEFT))System.out.println("Left");
if (evt.wasPressed(WRButtonEvent.RIGHT))System.out.println("Right");
if (evt.wasPressed(WRButtonEvent.DOWN))System.out.println("Down");
if (evt.wasPressed(WRButtonEvent.UP))System.out.println("Up");
if (evt.wasPressed(WRButtonEvent.PLUS))System.out.println("Plus");
}

}
少なくとも自分の環境では、接続時にエラーが出ることはほとんどなくなった。ただ、アプリ終了時のdisconnect()がどうしてもうまくいかない。Ctrl+Cとかでアプリを終了させると、ShutdownHookが利いてdisconnect()が呼ばれるところまでは行くのだが、きちんと切断しきってくれない。アプリが終了した後、Bluetooth接続が残ってしまう。まぁそのままでもいいんだけど、これ、きれいに切りたいなぁ...

2009/09/22

I2C LCD用ライブラリを作ってみた

ビーコン菅原の組み込みがらみ | Arduinoで I2C LCD」を参考にして、ストロベリーリナックスのI2C低電圧キャラクタ液晶モジュール(16x2行)を動かしてみた。
I2C LCD on arduino
ピン2本だけでLCDが制御できてしまうのは楽チンだ。
ついでにアイコンの表示制御機能も追加した上で、ライブラリ化してみた。
スケッチはとにかく簡潔にしたいのである...;-)

I2C LCD(ST7032i)用ライブラリ:NsLCD-0.1.zip

ちなみに、ライブラリを置く場所は、IDEのバージョンが0017なら、「Sketchbook location」直下の「libraries」(無ければ自分で作ればよい)、IDEのバージョンが0016以前なら、IDEインストール場所の「Arduino\hardware\libraries」。

NsLCDを使ったスケッチはこんな感じになる。
#include <Wire.h>
#include <NsLCD.h>

// NsLCD library for I2C LCD(ST7032i) sample sketch
// YKO[2009/09/21]

void setup()
{
// initialize I2C LCD
Wire.begin( 0 ); // I2C master mode
NsLCD.init();
}

void loop()
{
NsLCD.clear();

NsLCD.moveCursor(0,0);
NsLCD.print("cyclic landscape");

NsLCD.moveCursor(0,1);
NsLCD.print(" by YKO");

NsLCD.displayAntennaIcon(true);
NsLCD.displayTelIcon(true);
NsLCD.displaySoundIcon(true);
NsLCD.displayOutputIcon(true);
NsLCD.displayUpDownIcon(true,true);
NsLCD.displayKeyIcon(true);
NsLCD.displayMuteIcon(true);
NsLCD.displayBatteryIcon(true,3);
NsLCD.displayNumIcon(true);

delay(1000);

NsLCD.home();
for(int i=0;i<16;i++) NsLCD.write('@');

NsLCD.displayAntennaIcon(false);
NsLCD.displayTelIcon(false);
NsLCD.displaySoundIcon(false);
NsLCD.displayOutputIcon(false);
NsLCD.displayUpDownIcon(false,false);
NsLCD.displayKeyIcon(false);
NsLCD.displayMuteIcon(false);
NsLCD.displayBatteryIcon(true,0);
NsLCD.displayNumIcon(false);

delay(1000);
}
使い方のポイントは以下の通り。
  • init()する前に、必ずI2Cライブラリの方でWire.begin(0)すること。

  • init()では500msecほど、clear()では200msecほど、それぞれ待ち時間が発生する。

  • moveCursor()でカーソルを動かして、print()で文字列、write()で文字、を表示。

  • display??Icon()で各アイコンの表示状態を制御。

とりあえず問題無く動いてるみたい。

2009/09/20

GPS受信機シールド用ライブラリを作ってみた

シルバーウィークのうちに、arduinoで少しまとめて遊びたい。
というわけで、先日入手したGPSシールド用のライブラリを書いて、動かしてみている。
GPS受信機シールド動作中
ベースはgpsrobocarブログで公開されているテスト用スケッチ
ライブラリ化してみたら、スケッチがすっきりして気持ちがいい。
せっかく作ったので、とりあえず公開しておく。

GPS受信機シールド用ライブラリ:NsGH81-0.1.zip

以下にNsGH81を使ったスケッチのサンプルを載せておく。
シリアルが一本しかないDuemilanoveなどで動くよう、NewSoftSerialを使っている。純正のSerialライブラリを使ってももちろん動くし、手持ちのMegaでも正常に動作した。また、GH-81用に作ったものだが、古野バイナリでデータをやり取りする他の機種(GH-80など)でも動くはず。確認はできないけど。
#include <NsGH81.h>
#include <NewSoftSerial.h>

// NsGH81 sample sketch
// YKO[2009/09/20]

// serial pin
#define SER_RX 2
#define SER_TX 3

// serial baud rate
#define BAUD_RATE_LOCAL 9600 // Serial
#define BAUD_RATE_GPS 9600 // NewSoftSerial

// GPS module's LED pins
#define GPS_LED1 9
#define GPS_LED2 8

// setup SoftwareSerial port
NewSoftSerial mySerial = NewSoftSerial(SER_RX,SER_TX);

// buffer
char buff[50];

void setup()
{
// initialize pin modes
pinMode(SER_RX,INPUT);
pinMode(SER_TX,OUTPUT);

// initialize serial ports
Serial.begin(BAUD_RATE_LOCAL);
mySerial.begin(BAUD_RATE_GPS);

// initialize GPS
NsGH81.init(recieveLocation,recieveTime,GPS_LED1,GPS_LED2);
NsGH81.setStartCmd(buff);
mySerial.print(buff);
}

void loop()
{
int d = mySerial.read();
if( d>=0 ){
NsGH81.update(d);
}
}

void recieveLocation(NSGH81_LOCATION g)
{
int alt_0 = int(floor(g.alt));
int alt_1 = int(round((g.alt-alt_0)*10.0));
sprintf(buff, "lat=%+03d-%02d-%02d.%03d, ", g.lat.deg, g.lat.min, g.lat.sec, g.lat.msec);
Serial.print(buff);
sprintf(buff, "lon=%+04d-%02d-%02d.%03d, ", g.lon.deg, g.lon.min, g.lon.sec, g.lon.msec);
Serial.print(buff);
sprintf(buff, "alt=%d.%01d[m], nsat=%d, status=%d", alt_0, alt_1, g.nsat, g.status );
Serial.println(buff);
}

void recieveTime(NSGH81_TIME g)
{
sprintf(buff, "(20%02d/%02d/%02d %02d:%02d:%02d)", g.year, g.month, g.day, g.hour, g.min, g.sec);
Serial.println(buff);
Serial.println("-----");
}
ライブラリの使い方を整理すると以下の通り。
  • setup()で...

    • init()でコールバック関数とシールド上のLEDにつないだピン番号を設定

    • setStartCmd()で測位動作設定用のコマンドを作成し、シリアルを使ってモジュールへ書き込み

  • loop()で...

    • シリアルからデータを1バイト読んで、読んだ1バイトのデータを引数にしてupdate()をコール、を延々繰り返し。ライブラリ内部で読み込んだデータはパースされ、パース結果(測位結果と時刻)が得られる度に、結果を引数にしてコールバック関数がコールされる。つまり、コールバック関数の中に所望の処理を書いておけばよい。

さて、GPSモジュールは無事つながった。何しよう...笑

2009/09/05

GPS受信機シールド入手

GPSロボットカーコンテスト向けに無償配布されていたArduino用のGPS受信機シールド、首尾よく入手できた。
GPS受信機シールド
GPSモジュールは古野電気のGH-81だそうだ。赤い基板がかっこいい。
とりあえずArduinoにつないでみて、手持ちの秋月GT-720Fと性能などを比較してみるつもり。

2007/10/31

距離算出APIデモの動作を修正

LatLng2Distance:::緯度経度から距離算出API」のGoogle Mapsを使ったデモ、久しぶりに(数ヶ月ぶりに?)見てみたら動作がおかしい。

どうやらGoogle Maps APIの仕様が少し変わっていたようです。仕様変更の時期が良く分かりませんが、動作がおかしいまま長く放置してしまっていたかも・・・

今回の問題発生の原因になっていた仕様変更は以下の二点。
  • 【存在しないoverlayをremoveOverlayするとエラーが出るようになった】
    前はエラー出なかったんですよね・・・本来、エラー出すのが正しいんでしょうけど。
  • 【GPolyline上でのクリックイベントの扱いが変わった】
    具体的にどう変わったか、ちゃんと確かめられてませんが、とにかく、何か変わったみたい。
とりあえずこれらの変更に対応してデモを修正しました。問題なく動いていると思います。