MQTTでAWS IoTやSORACOM beamにAndroidから接続する

# 概要
MQTTをお試しするためにAndroidからAWS IoTやSORACOM beamに接続してみました。
AWS IoTとの接続はMQTTクライアントの[Paho](https://www.eclipse.org/paho/clients/java/)を使おうと思ったのですが、AWS SDK for Androidのjavadocを読んでみるとAWS IoT関連のクラスがあったので使ってみました。

※ここではブローカー側の設定については触れません。クライアント側の実装に絞って説明します。

# 準備
まずは依存関係にAWS SDKを追加。

```groovy
dependencies {
    compile 'com.amazonaws:aws-android-sdk-core:2.+'
    compile 'com.amazonaws:aws-android-sdk-iot:2.+'
} 
```
ビルドすると、Pahoのライブラリも追加されるので、AWS SDKのMQTT関連の実装はPahoのラッパーになっているようです。

# ServiceへのAWS IoTの実装

AndroidのサービスクラスにAWS IoTとの通信をおこなう処理を実装しました。

## 接続開始
AWS ConsoleでAWS IoTのセットアップをおこなうとエンドポイント、証明書と秘密鍵が生成されます。
（参考: [AWS IoTとRuby製MQTTクライアントでPub/Subしてみた](http://qiita.com/hiroeorz@github/items/f933ad1158a08506922a)）

生成された情報を使って接続を行います。
接続にはキーストアが必要となるため、アセットに格納しておいた証明書と秘密鍵を読み出しキーストアに格納することにしました。 (証明書と秘密鍵の扱いは本番運用ではちゃんと考えないといけないです。) 

ACCOUNT_ENDPOINT_PREFIXは、XXXXXXXXXXX.iot.REGION.amazonaws.comの先頭 (XXXXXXXXXXX) の部分です。
CLIENT_ID、CERT_ID、KEY_STORE_NAME、KEY_STORE_PASSWORDは適当に決めます。

```java
public class MqttService extends Service {
    private AWSIotMqttManager mqttManager;
    private static final String CLIENT_ID = "xxxxx";
    private static final String ACCOUNT_ENDPOINT_PREFIX = "xxxxx";
    private static final String CERT_ID = "xxxxx";
    private static final String KEY_STORE_NAME = "xxxxx.jks";
    private static final String KEY_STORE_PASSWORD = "xxxxx";
    private static final String CERT_FILE = "xxxxx-certificate.pem.crt";
    private static final String PRIVATE_KEY_FILE = "xxxxx-private.pem.key";
    private String keyStorePath;
    private KeyStore keyStore;

    @Override
    public void onCreate() {
        super.onCreate();
        mqttManager = new AWSIotMqttManager(CLIENT_ID, Region.getRegion(Regions.AP_NORTHEAST_1), ACCOUNT_ENDPOINT_PREFIX);
        keyStorePath = getFilesDir().getAbsolutePath();
        boolean isPresent = AWSIotKeystoreHelper.isKeystorePresent(keyStorePath, KEY_STORE_NAME);
        if (isPresent) {
            keyStore = AWSIotKeystoreHelper.getIotKeystore(CERT_ID, keyStorePath, KEY_STORE_NAME, KEY_STORE_PASSWORD);
        } else {
            saveCertificateAndPrivateKey(keyStorePath);
            keyStore = AWSIotKeystoreHelper.getIotKeystore(CERT_ID, keyStorePath, KEY_STORE_NAME, KEY_STORE_PASSWORD);
        }
        mqttManager.connect(keyStore, new AWSIotMqttClientStatusCallback() {
            @Override
            public void onStatusChanged(AWSIotMqttClientStatus status, Throwable throwable) {
                Log.d(TAG, "AWSIotMqttClientStatus changed.");
            }
        });
    }
    private void saveCertificateAndPrivateKey(String keyStorePath) {
        String cert = assetsFileToString(CERT_FILE);
        String priv = assetsFileToString(PRIVATE_KEY_FILE);
        AWSIotKeystoreHelper.saveCertificateAndPrivateKey(CERT_ID, cert, priv, keyStorePath, KEY_STORE_NAME, KEY_STORE_PASSWORD);
    }
 
    private String assetsFileToString(String file) {
        // 省略: アセットのファイルを読みだし文字列として返す。
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mqttManager != null) {
            mqttManager.disconnect();
        }
    }
}
```
## メッセージの送信 (Pub) 
JSONメッセージを引数にpublishStringメッセージを叩くだけです。
トピックは適当に決めます。

```java
if (mqttManager != null) {
    mqttManager.publishString(msg, TOPIC, AWSIotMqttQos.QOS0);
}
```
## メッセージの受信 (Sub) 
すみません、、、試してません。`subscribeToTopic`メソッドでSubscribeしてコールバックを受け取るだけと思います。

# 接続先をSORACOM beamに変更する
AWS IoTのラッパーを使わずに、Pahoのライブラリを直接実行します。

## 接続開始

キーストアが要らないのでとてもシンプルになります。

```java
    private MqttClient mqttClient;
    private static final String BROKER_ENDPOINT = "tcp://beam.soracom.io:xxxx";

   @Override
    public void onCreate() {
        super.onCreate();
        try {
            mqttClient = new MqttClient(BROKER_ENDPOINT, CLIENT_ID, new MemoryPersistence());
            MqttConnectOptions connOpts = new MqttConnectOptions();
            connOpts.setCleanSession(true);
            mqttClient.connect(connOpts);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mqttClient != null) {
            try {
                mqttClient.disconnect();
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }
```

## メッセージの送信 (Pub) 
こちらはJSONを一旦MqttMessageに格納してやる必要があります。

```java
if (mqttClient != null) {
    MqttMessage sendMessage = new MqttMessage(msg.getBytes());
    sendMessage.setQos(0);
    mqttClient.publish(TOPIC, sendMessage);
}
```
## メッセージの受信 (Sub) 
すみません、、、こちらも試してません。`subscribe`メソッドと`setCallback`メソッドで実現できるはず。

# 参考情報
* GitHub AWS SDK for Android, https://github.com/aws/aws-sdk-android
* AWS SDK for Android javadoc, http://docs.aws.amazon.com/AWSAndroidSDK/latest/javadoc/
* Paho, https://www.eclipse.org/paho/clients/java/
* [AWS IoTとRuby製MQTTクライアントでPub/Subしてみた](http://qiita.com/hiroeorz@github/items/f933ad1158a08506922a)