/* FPLUGController.java Copyright (c) 2015 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.fplug.fplug; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import java.io.IOException; import java.util.Calendar; import java.util.HashSet; import java.util.Queue; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * This class provides functions of controlling F-PLUG. * <p> * This class provides the following functions: * <li>Connect to F-PLUG</li> * <li>Send requests to F-PLUG</li> * <li>Receive responses from F-PLUG</li> * This class instance can handle only single F-PLUG. * This class is thread safe. * </p> * * @author NTT DOCOMO, INC. */ public class FPLUGController { public interface FPLUGConnectionListener { void onConnected(String address); void onDisconnected(String address); void onConnectionError(String address, String message); } private static final int REQUEST_QUEUE_SIZE = 4; private static final int TIMEOUT = 5; private Set<FPLUGConnectionListener> mConnectionListenerSet = new HashSet<>(); private BluetoothDevice mTargetDevice; private BluetoothSocket mSocket; private FPLUGRequest mCurrentRequestData; private FPLUGSender mSender; private FPLUGReceiver mReceiver; private boolean mIsConnected = false; private boolean mIsConnecting = false; private ScheduledExecutorService mTimeoutTimer = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture mFuture; private Queue<FPLUGRequest> mRequestQueue = new ArrayBlockingQueue<>(REQUEST_QUEUE_SIZE); public FPLUGController(BluetoothDevice device) throws IllegalArgumentException { if (device == null) { throw new IllegalArgumentException("device is null"); } this.mTargetDevice = device; } public String getAddress() { return mTargetDevice.getAddress(); } public synchronized boolean isConnected() { return mIsConnected; } public synchronized void connect(FPLUGConnectionListener listener) { if (listener != null) { mConnectionListenerSet.add(listener); } if (mIsConnected) { if (listener != null) { listener.onConnected(getAddress()); } return; } if (mIsConnecting) { return; } mIsConnecting = true; new FPLUGConnector(mTargetDevice, new FPLUGConnector.FPLUGConnectorEventListener() { @Override public void onConnected(BluetoothSocket socket) { mIsConnecting = false; connected(socket); } @Override public void onError(String message) { mIsConnecting = false; callOnError(message); } }).start(); } public synchronized void addConnectionListener(FPLUGConnectionListener listener) { mConnectionListenerSet.add(listener); } public synchronized void removeConnectionListener(FPLUGConnectionListener listener) { mConnectionListenerSet.remove(listener); } public synchronized void disconnect() { if (mIsConnected) { cancelTimer(); closeSocket(); stopReceiveThread(); callOnDisconnected(); mIsConnected = false; } } public void requestInitPlug(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.INIT, callback)); } public void requestCancelPairing(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.CANCEL_PAIRING, callback)); } public void requestWattHour(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.WATT_HOUR, callback)); } public void requestTemperature(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.TEMPERATURE, callback)); } public void requestHumidity(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.HUMIDITY, callback)); } public void requestIlluminance(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.ILLUMINANCE, callback)); } public void requestRealtimeWatt(final FPLUGRequestCallback callback) { checkCallback(callback); request(new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.REALTIME_WATT, callback)); } public void requestPastWattHour(Calendar date, final FPLUGRequestCallback callback) { checkDate(date); checkCallback(callback); FPLUGRequest data = new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.PAST_WATT, callback); data.setValue(date); request(data); } public void requestPastValues(Calendar date, final FPLUGRequestCallback callback) { checkDate(date); checkCallback(callback); FPLUGRequest data = new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.PAST_VALUES, callback); data.setValue(date); request(data); } public void requestSetDate(Calendar date, final FPLUGRequestCallback callback) { checkDate(date); checkCallback(callback); FPLUGRequest data = new FPLUGRequest(FPLUGRequest.REQUEST_TYPE.SET_DATE, callback); data.setValue(date); request(data); } public void requestLEDControl(boolean on, final FPLUGRequestCallback callback) { checkCallback(callback); FPLUGRequest.REQUEST_TYPE type = on ? FPLUGRequest.REQUEST_TYPE.LED_ON : FPLUGRequest.REQUEST_TYPE.LED_OFF; request(new FPLUGRequest(type, callback)); } private void checkCallback(FPLUGRequestCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback is null"); } } private void checkDate(Calendar date) { if (date == null) { throw new IllegalArgumentException("date is null"); } } private synchronized void connected(BluetoothSocket socket) { closeSocket(); mSocket = socket; mSender = new FPLUGSender(mSocket); mIsConnected = true; launchReceiveThread(); callOnConnected(); } private synchronized void callOnConnected() { for (FPLUGConnectionListener fplugConnectionListener : mConnectionListenerSet) { fplugConnectionListener.onConnected(getAddress()); } } private synchronized void callOnDisconnected() { for (FPLUGConnectionListener fplugConnectionListener : mConnectionListenerSet) { fplugConnectionListener.onDisconnected(getAddress()); } } private synchronized void callOnError(String message) { for (FPLUGConnectionListener fplugConnectionListener : mConnectionListenerSet) { fplugConnectionListener.onConnectionError(getAddress(), message); } } private synchronized void request(FPLUGRequest data) { if (!checkRequestQueue(data.getCallback())) { return; } mRequestQueue.add(data); if (!isRequesting()) { nextRequest(); } } private synchronized boolean checkRequestQueue(final FPLUGRequestCallback callback) { if (mRequestQueue.size() >= REQUEST_QUEUE_SIZE) { callback.onError("to many request. please wait"); return false; } return true; } private synchronized boolean isRequesting() { return mFuture != null; } private synchronized void nextRequest() { if (mRequestQueue.size() == 0) { mCurrentRequestData = null; return; } final FPLUGRequest data = mRequestQueue.poll(); if (!mIsConnected) { data.getCallback().onError("F-PLUG not connected"); nextRequest(); return; } mSender.executeRequest(data); mCurrentRequestData = data; mFuture = mTimeoutTimer.schedule(new Runnable() { @Override public void run() { onTimeout(); } }, TIMEOUT, TimeUnit.SECONDS); } private synchronized void onTimeout() { mCurrentRequestData.getCallback().onTimeout(); mFuture = null; mCurrentRequestData = null; nextRequest(); } private synchronized void launchReceiveThread() { if (mReceiver == null) { mReceiver = new FPLUGReceiver(getAddress(), mSocket, new FPLUGReceiver.FPLUGReceiverEventListener() { @Override public void onDisconnected() { disconnect(); } @Override public void onReceiveResponse(FPLUGResponse response) { onReceiveData(response, null); } @Override public void onReceiveError(String message) { onReceiveData(null, message); } }); mReceiver.start(); } } private synchronized void onReceiveData(FPLUGResponse response, String message) { if (mCurrentRequestData != null) { if (response != null) { mCurrentRequestData.getCallback().onSuccess(response); } else { mCurrentRequestData.getCallback().onError(message); } } cancelTimer(); nextRequest(); } private synchronized void stopReceiveThread() { if (mReceiver != null) { mReceiver.close(); mReceiver = null; } } private synchronized void cancelTimer() { if (mFuture != null) { mFuture.cancel(true); mFuture = null; } } private synchronized void closeSocket() { if (mSocket != null) { try { mSocket.close(); } catch (IOException e) { //do not something } mSocket = null; } } }