2013年4月26日金曜日

Android GCM を使う

サーバからAndroid 端末へメッセージをデータ送信できる GCM を使いました。(無料です)
GCM: Getting Started の手順に沿って「サーバからメッセージを送って、Logcatに表示する」仕組みを作ります。

■Google API Project の作成:
  1. Google APIs Console page へアクセス
  2. まだAPI Projectを作成していない場合、下記のような画面が現れます。
    API Projectを既に作成している場合、DashBoard が表示されます。この画面の API Projectドロップダウンメニュー(画面左上)から、Other Projects > Create. で、新規プロジェクトを作成できます。
  3. Create Project ボタンをクリックすると、ブラウザのURLが下記のように変わります。
    https://code.google.com/apis/console/#project:4815162342:services
  4. #project:以降の値をメモしておきます(3.の場合、4815162342 )。この値がプロジェクトIDで、今後 GCM のsender ID として使われます。

■GCM サービスを有効にする:
  1. 左側のメニューより、Services を選択します。
  2. Google Cloud Messaging for Android を on にします。
  3. 利用規約が表示されるので、I agreeにチェックを入れ、Accept をクリックします。

■API キーの取得:
  1. 左側のメニューより、API Access を選択すると、下記のような画面が表示されます。
    IP アドレス制限をかけない場合は、このAPI キーを使用する。制限をかける場合は2へ
  2. Create new Server key ボタンをクリックする。下記ダイアログが表示されるので、
  3. サーバのIPアドレスを入力し、Create をクリックすると、下記のように Key for server apps (with IP locking)  が追加される。

■Helperライブラリのインストール:
  • SDK Manager を起動し、Extras > Google Cloud Messaging for Android をインストールする。Android SDKのディレクトリ内は下記のようになります。

■Androidアプリの作成:
  1. 新規プロジェクトを作成 ( minSdkVersionは、8以上に設定 )し、プロジェクトのビルドパスへ、gmc.jar を追加します。
  2. AndroidManifest.xmlを次のようにします。
    1. sdkの設定 (この通りになっているはず)
      <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="xx" />
    2. パーミッションの設定を追加
      <permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" />
      <uses-permission android:name="my_app_package.permission.C2D_MESSAGE" /> 
      my_app_package は、マニフェストのpackage名を入れます。
    3. 以下のパーミッションも追加
      <!-- App receives GCM messages. -- >
      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
      <!-- GCM connects to Google Services. -- >
      <uses-permission android:name="android.permission.INTERNET" />
      <!-- GCM requires a Google account. -- >
      <uses-permission android:name="android.permission.GET_ACCOUNTS" />
      <!-- Keeps the processor from sleeping when a message is received. -- >
      <uses-permission android:name="android.permission.WAKE_LOCK" />
    4. 以下の broadcast receiver を追加
      <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
          <action android:name="com.google.android.c2dm.intent.RECEIVE" />
          <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
          <category android:name="my_app_package" />
        </intent-filter>
      </receiver>
    5. 以下のサービスを追加
      <service android:name=".GCMIntentService" />

■サービスクラスの作成:
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;

public class GCMIntentService extends GCMBaseIntentService {
 private static final String TAG = GCMIntentService.class.getSimpleName();

 @Override
 protected void onError(Context arg0, String arg1) {
  Log.d(TAG, "onError");
 }

 @Override
 protected void onMessage(Context context, Intent intent) {
  Log.d(TAG, "onMessage");
 }

 @Override
 protected void onRegistered(Context context, String regId) {
  Log.d(TAG, "regId registered:" + regId);
 }

 @Override
 protected void onUnregistered(Context context, String regId) {
  Log.d(TAG, "regId not registered:" + regId);
 }

 @Override
 protected boolean onRecoverableError(Context context, String errorId) {
  // TODO Auto-generated method stub
  return super.onRecoverableError(context, errorId);
 }
}


■MainActivityの修正:

import文追加
import com.google.android.gcm.GCMRegistrar;

onCreate() メソッド内に以下を追加
SENDER_ID は、Google API Project の作成 の 4 でメモした値を定数定義します。
端末のデバイスIDを知りたいので、LogcatにregIdを出力します。
GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
final String regId = GCMRegistrar.getRegistrationId(this);
Log.d(TAG, "regId :" + regId);

if (regId.equals("")) {
  GCMRegistrar.register(this, SENDER_ID);
} else {
  Log.v(TAG, "Already registered");
}

■サーバ側のプログラム作成:
  1. GCM SDK の gcm-server.jar をサーバのクラスパスが通っているところへコピーします。
  2. GCMServer.java 作成(サンプルとほぼ変わりません)
    デバイスID・・・作成したAndroidアプリを端末へインストールして、Logcatに出力されたID
    Google API Key ・・・APIプロジェクトを登録したときに取得したもの
    import java.util.ArrayList;
    import com.google.android.gcm.server.*;
    
    public class GCMServer {
      
      public static void main(String[] args) {
      try{
        String sendMessage = "say hello from my server!";
        ArrayList devices = new ArrayList();
        devices.add( デバイスID );
        Sender sender = new Sender( Google API Key );
        Message message = new Message.Builder().addData("message", sendMessage).build();
        MulticastResult result = sender.send(message, devices, 5);
      } catch (Exception e) {
        System.out.println(e);
      }
      }
    }
    
    

    コンパイルは通りましたが、実行時にエラーが発生しました。
    Exception in thread "main" java.lang.NoClassDefFoundError: org/json/simple/parser/ParseException

    StackOverFlow に載っていました。 json-simple-1.1.1.jar を追加し、再度挑戦したら、動きました。

■GCMIntentService.javaを修正:

サーバからのメッセージをログに出力したいので、GCMIntentService.java の onMessage() メソッドを修正します。
String str = intent.getStringExtra("message");
Log.d(TAG, "onMessage str:" + str);

再度端末へインストールして、サーバ側のプログラムを実行すると、Logcatにでました。
 onMessage str:say hello from my server!

日本語も表示できるか確認します。
GCMServer.java のメッセージ文字列に日本語を追加し、実行します。
String sendMessage = "say hello from my server! 日本語いけるかな?";

無事でました。
 onMessage str:say hello from my server! 日本語いけるかな?

2013年4月19日金曜日

AndroidとELM327で車と会話(OBD2)



「車と会話できる方法がある」という噂を聞いてそれについて調べてみました。


■準備

1.ELM327 OBD2 スキャンツール
 ⇒amazon等で2000円程度で購入できます。

2.android端末(SO-02C)
 ⇒bluetooth使えればOKです。

3.車(2004年式Suzuki Swift)
 ⇒ELM327に対応していない車も結構あります。


■構成

イメージ的にはこんな感じです。

このandroidで車の情報を収集できます。


ELM327の実物です



ELM327スキャンツールを車に差し込んでアンドロイド端末とbluetoothで通信します。


■OBDプロトコル

OBDとはOn Board Diagnostic Systemの略で、車に搭載されたコンピューターが故障の診断を行います。
そして今回やりたい事は車に搭載されているコンピューターに対してELM327経由で問い合わせをし情報を収集する事です。

故障の診断を行うのですからandroidから「故障情報」が取得できるのだろうと思うのですが、
実は故障情報だけでなく走行中の車のスピードやエンジンの回転数等がとれます。

私はどちらかと言うと故障情報よりはスピードやエンジンの回転数を取得して、androidでスピードメーターアプリなんかが興味あります。


■ペアリング

まずはELM327とペアリングを行う必要があります。流れとしては以下の通りです。
1.車にELM327を差し込みます。
  ⇒車ごとに差し込み口は異なり、見え難いところだったりしますので
   注意が必要です。私は20分位探しまくってやっと見つけました。。
  ⇒正しく差し込むと赤いランプが点灯します。(エンジンかけなくとも)

2.android端末でペアリング操作
  ⇒[設定]-[無線とネットワーク]-[Bluetooth設定]・・・
   ELM327のデバイスを選択する。私の場合は[CHX]でした。pinコードは6789。


■プログラミング

1.権限
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

2.UUID
  bluetoothで使用するUUIDは
  "00001101-0000-1000-8000-00805F9B34FB"とします。
  これは、Bluetooth SPP(Serial Port Profile)のUUIDです。

3.bluetoothでコネクト
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
socket = device.createRfcommSocketToServiceRecord(uuid);
socket.connect(); // この処理は最大12秒位かかるので非同期処理が良さそうです。

  ※deviceはELM327のBluetoothDeviceのインスタンスです。
   bluetoothの接続についての詳細は割愛します。

4.初期化コマンド
  いよいよコマンドをELM327に送ります。
  "atz", "ate0", "atl0" という3つのATコマンドを送ります。

OutputStream outputStream = socket.getOutputStream();
outputStream.write("atz\r".getBytes()); // ★コマンドの最後は\r
outputStream.flush(); // 送信
// 送信直後に応答を待ちます
// 応答は '>'(プロンプト) を受けるまで読み続けます
ByteArrayOutputStream buf = new ByteArrayOutputStream();
while( true ) {
 InputStream in = socket.getInputStream();
 byte[] buffer = new byte[1];
 if( in.read(buffer) > 0 ) {
  buf.write(buffer);
  if( new String(buffer).indexOf(">") >= 0 ) {
   break; // ★プロンプトを終端とする。
  }
 }
}

※ATコマンド
    http://elmelectronics.com/ELM327/AT_Commands.pdf

  以下の通り結果は以下の通りです。

  送信[atz]
  受信[0d 0d 45 4c 4d 33 32 37 20 76 31 2e 35 0d 0d 3e](hex)
     \r \r  E  L  M  3  2  7 △  v  1  .  5 \r \r  >
  送信[ate0]
  受信[61 74 65 30 0d 4f 4b 0d 0d 3e](hex)
     a  t  e  0 \r  O  K \r \r  >
  送信[atl0]
  受信[4f 4b 0d 0d 3e]
     O  K \r \r  >

5.走行自動車のスピードを発行
  4のコマンドを"01 0D"とします。

  結果は以下の通りです。

  送信[010D]
  受信[42 55 53 20 49 4e 49 54 3a 20 4f 4b 0d 37 46 20 30 31 20 31 31 20 0d 0d 3e](hex)
     B  U  S △  I  N  I  T  : △  O  K \r  7  F △  0  1 △  1  1 △ \r \r  >

  BUS INIT:OK までは良かったのですがその後の
  7F 01 11 という回答はどうもスピード情報ではないようです。
  何度"01 0D"コマンドを発行しても 7F 01 11 でした。
  この結果は残念ながら「失敗」の様です。

  ※コマンドのPID一覧
    http://en.wikipedia.org/wiki/OBD-II_PIDs


■最後に

  スピードの情報は一番とりたかった情報なのに残念です。
  もしかして試した車が2004 Suzuki Swift 15で、対応していないのかもしれません。

  進展があったらブログを更新したいと思います。