/* AWSIotController.java Copyright (c) 2016 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.awsiot.cores.core; import android.os.AsyncTask; import android.util.Log; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.internal.StaticCredentialsProvider; import com.amazonaws.mobileconnectors.iot.AWSIotMqttClientStatusCallback; import com.amazonaws.mobileconnectors.iot.AWSIotMqttManager; import com.amazonaws.mobileconnectors.iot.AWSIotMqttMessageDeliveryCallback; import com.amazonaws.mobileconnectors.iot.AWSIotMqttNewMessageCallback; import com.amazonaws.mobileconnectors.iot.AWSIotMqttQos; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.amazonaws.services.iot.AWSIotClient; import com.amazonaws.services.iot.model.DescribeEndpointRequest; import com.amazonaws.services.iot.model.DescribeEndpointResult; import com.amazonaws.services.iotdata.AWSIotDataClient; import com.amazonaws.services.iotdata.model.GetThingShadowRequest; import com.amazonaws.services.iotdata.model.GetThingShadowResult; import com.amazonaws.services.iotdata.model.UpdateThingShadowRequest; import com.amazonaws.services.iotdata.model.UpdateThingShadowResult; import org.deviceconnect.android.deviceplugin.awsiot.remote.BuildConfig; import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; /** * AWSIotを制御するクラス. */ public class AWSIotController { private static final boolean DEBUG = BuildConfig.DEBUG; private static final String TAG = "AWS"; private static final String END_POINT_ADDRESS = "endpointAddress"; /** AWSIoTのClient. */ private AWSIotClient mIotClient; /** AWSIoTのDataClient. */ private AWSIotDataClient mIotDataClient; /** AWSIoTのMqtt Manager. */ private static AWSIotMqttManager mMqttManager; /** 証明書のプロバイダ. */ private AWSCredentialsProvider mCredentialsProvider; /** 接続フラグ. */ private boolean mIsConnected = false; /** endpoint情報. */ private String mAWSIotEndPoint = "Not Connected"; private List<OnAWSIotEventListener> mOnAWSIotEventListeners = new CopyOnWriteArrayList<>(); public interface LoginCallback { void onLogin(Exception err); } public interface GetShadowCallback { void onReceivedShadow(String thingName, String result, Exception err); } public interface UpdateShadowCallback { void onUpdateShadow(String result, Exception err); } public interface MessageCallback { void onReceivedMessage(String topic, String message, Exception err); } public interface OnAWSIotEventListener { void onLogin(); void onConnected(); } public void addOnAWSIotEventListener(OnAWSIotEventListener listener) { mOnAWSIotEventListeners.add(listener); } public void removeOnAWSIotEventListener(OnAWSIotEventListener listener) { mOnAWSIotEventListeners.remove(listener); } public boolean isLogin() { return mAWSIotEndPoint != null; } /** * AWSIoTサーバにログインします. * * @param accessKey アクセスキー * @param secretKey シークレットキー * @param region リージョン */ public void login(final String accessKey, final String secretKey, final Regions region, final LoginCallback callback) { if (accessKey == null || secretKey == null || region == null) { if (callback != null) { callback.onLogin(new RuntimeException("Arguments is null.")); } return; } BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); mCredentialsProvider = new StaticCredentialsProvider(credentials); mIotClient = new AWSIotClient(mCredentialsProvider); mIotClient.setRegion(Region.getRegion(region)); mIotDataClient = new AWSIotDataClient(mCredentialsProvider); new DescribeEndpointTask() { @Override protected void onPostExecute(final AsyncTaskResult<String> result) { Exception exception = null; if (result.getError() == null) { JSONObject json; try { json = new JSONObject(result.getResult()); if (json.has(END_POINT_ADDRESS)) { String endpoint = json.getString(END_POINT_ADDRESS); mAWSIotEndPoint = endpoint; mIotDataClient.setEndpoint(endpoint); for (OnAWSIotEventListener l : mOnAWSIotEventListeners) { l.onLogin(); } connectMQTT(); } else { exception = new Exception("Not found endpointAddress."); } } catch (JSONException e) { exception = e; } } else { exception = result.getError(); } callback.onLogin(exception); } }.execute(); } /** * AWS IoTサーバからログアウトします. */ public void logout() { if (DEBUG) { Log.i(TAG, "AWSIotController#disconnect"); } if (mIotDataClient != null) { mIotDataClient.shutdown(); mIotDataClient = null; } if (mIotClient != null) { mIotClient.shutdown(); mIotClient = null; } mAWSIotEndPoint = null; mCredentialsProvider = null; disconnectMQTT(); } void getShadow(final String name, final GetShadowCallback callback) { if (callback == null) { throw new NullPointerException("callback is null."); } if (mAWSIotEndPoint == null) { callback.onReceivedShadow(name, null, new RuntimeException("No Connect to the AWS IoT Server.")); return; } GetShadowTask task = new GetShadowTask(name) { @Override protected void onPostExecute(final AsyncTaskResult<String> result) { callback.onReceivedShadow(name, result.getResult(), result.getError()); } }; task.execute(); } void updateShadow(final String name, final String key, final Object value, final UpdateShadowCallback callback) { if (callback == null) { throw new NullPointerException("callback is null."); } if (mAWSIotEndPoint == null) { callback.onUpdateShadow(null, new RuntimeException("No Connect to the AWS IoT Server.")); return; } UpdateShadowTask updateShadowTask = new UpdateShadowTask() { @Override protected void onPostExecute(final AsyncTaskResult<String> result) { callback.onUpdateShadow(result.getResult(), result.getError()); } }; updateShadowTask.setThingName(name); updateShadowTask.setState(makeJson(key, value).toString()); CountDownLatch latch = new CountDownLatch(1); updateShadowTask.setLatch(latch); updateShadowTask.execute(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * AWSIotへ送信するjson作成 * * @param key キー * @param value 値 * @return json */ private JSONObject makeJson(final String key, final Object value) { JSONObject jsonRoot = new JSONObject(); JSONObject jsonState = new JSONObject(); JSONObject jsonDesired = new JSONObject(); try { jsonDesired.put(key, value); jsonState.put("reported", jsonDesired); jsonRoot.put("state", jsonState); } catch (JSONException e) { if (DEBUG) { Log.e(TAG, "json error.", e); } // TODO: エラー処理 } return jsonRoot; } /** * Endpoint取得Task */ private class DescribeEndpointTask extends AsyncTask<Void, Void, AsyncTaskResult<String>> { @Override protected AsyncTaskResult<String> doInBackground(final Void... voids) { try { DescribeEndpointRequest request = new DescribeEndpointRequest(); DescribeEndpointResult result = mIotClient.describeEndpoint(request); return new AsyncTaskResult<>(result.toString()); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error on DescribeEndpointTask", e); } return new AsyncTaskResult<>(e); } } } /** * Shadowを取得するAsyncTask. */ private class GetShadowTask extends AsyncTask<Void, Void, AsyncTaskResult<String>> { /** Thing名. */ private final String thingName; /** * 名前を指定して初期化. * * @param name 名前 */ GetShadowTask(final String name) { thingName = name; } @Override protected AsyncTaskResult<String> doInBackground(final Void... voids) { try { GetThingShadowRequest getThingShadowRequest = new GetThingShadowRequest().withThingName(thingName); GetThingShadowResult result = mIotDataClient.getThingShadow(getThingShadowRequest); byte[] bytes = new byte[result.getPayload().remaining()]; result.getPayload().get(bytes); String resultString = new String(bytes); return new AsyncTaskResult<>(resultString); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error on GetShadowTask", e); } return new AsyncTaskResult<>(""); } } } /** * Shadowを更新するAsyncTask. */ private class UpdateShadowTask extends AsyncTask<Void, Void, AsyncTaskResult<String>> { /** Thing名. */ private String thingName; /** 更新するState. */ private String updateState; /** 同期用CountDownLatch インスタンス. */ private CountDownLatch mLatch; /** * Thing名を設定. * * @param name Thing名 */ void setThingName(final String name) { thingName = name; } /** * 同期用CountDownLatch インスタンスを設定. * @param latch インスタンス. */ void setLatch(final CountDownLatch latch) { mLatch = latch; } /** * Stateを設定. * * @param state State */ void setState(final String state) { updateState = state; } @Override protected AsyncTaskResult<String> doInBackground(final Void... voids) { try { UpdateThingShadowRequest request = new UpdateThingShadowRequest(); request.setThingName(thingName); ByteBuffer payloadBuffer = ByteBuffer.wrap(updateState.getBytes()); request.setPayload(payloadBuffer); UpdateThingShadowResult result = mIotDataClient.updateThingShadow(request); byte[] bytes = new byte[result.getPayload().remaining()]; result.getPayload().get(bytes); String resultString = new String(bytes); mLatch.countDown(); return new AsyncTaskResult<>(resultString); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error on UpdateShadowTask", e); } mLatch.countDown(); return new AsyncTaskResult<>(e); } } } /** * MQTTへの接続. */ private void connectMQTT() { if (DEBUG) { Log.i(TAG, "connectMQTT"); } if (mMqttManager != null) { return; } String clientId = UUID.randomUUID().toString(); mMqttManager = new AWSIotMqttManager(clientId, mAWSIotEndPoint); try { mMqttManager.connect(mCredentialsProvider, new AWSIotMqttClientStatusCallback() { @Override public void onStatusChanged(final AWSIotMqttClientStatus status, final Throwable throwable) { if (DEBUG) { Log.d(TAG, "MQTT Status = " + String.valueOf(status)); if (throwable != null) { Log.e(TAG, "MQTT Error", throwable); } } if (status == AWSIotMqttClientStatus.Connected) { // 接続済みとする if (!mIsConnected) { mIsConnected = true; for (OnAWSIotEventListener l : mOnAWSIotEventListeners) { l.onConnected(); } } } else { mIsConnected = false; } } }); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Connection error.", e); } } } private void disconnectMQTT() { if (DEBUG) { Log.i(TAG, "AWSIotController#disconnectMQTT"); } if (mMqttManager != null) { mMqttManager.disconnect(); mMqttManager = null; } mIsConnected = false; } public void subscribe(final String topic, final MessageCallback callback) { if (DEBUG) { Log.d(TAG, "********* subscribe: " + topic); } if (mMqttManager == null) { return; } try { mMqttManager.subscribeToTopic(topic, AWSIotMqttQos.QOS0, new AWSIotMqttNewMessageCallback() { @Override public void onMessageArrived(final String topic, final byte[] data) { try { callback.onReceivedMessage(topic, new String(data, "UTF-8"), null); } catch (UnsupportedEncodingException e) { callback.onReceivedMessage(topic, null, e); } } }); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "subscribe error. topic=" + topic); } } } public void unsubscribe(final String topic) { if (DEBUG) { Log.d(TAG, "********* unsubscribe: " + topic); } if (mMqttManager == null) { return; } try { mMqttManager.unsubscribeTopic(topic); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "unsubscribe error. topic=" + topic); } } } /** * MQTTでtopicへメッセージを発行する. * * @param topic topicのURI * @param msg メッセージ */ public boolean publish(final String topic, final String msg) { if (DEBUG) { Log.i(TAG, "publish topic:" + topic); } if (mMqttManager == null) { return false; } try { mMqttManager.publishString(msg, topic, AWSIotMqttQos.QOS0, new AWSIotMqttMessageDeliveryCallback() { @Override public void statusChanged(MessageDeliveryStatus status, Object userData) { if (DEBUG) { Log.i(TAG, "AWSIotController#publish: MessageDeliveryStatus=" + status); } } }, null); return true; } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Publish error." + e.toString()); } return false; } } /** * 接続状態を返す. * @return true(接続中) / false(切断中) */ public Boolean isConnected() { return mIsConnected; } /** endpoint情報取得関数 */ public String getAWSIotEndPoint() { return mAWSIotEndPoint; } }