diff --git a/.gitignore b/.gitignore
index c6cbe56..61571f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,10 @@
.DS_Store
/build
/captures
+<<<<<<< HEAD
+<<<<<<< HEAD
+app/src/main/java/ca/xlight/demoapp/SDK/CloudAccount.java
+=======
+>>>>>>> parent of 99d739f... clean
+=======
+>>>>>>> parent of 99d739f... clean
diff --git a/.idea/dictionaries/sunboss.xml b/.idea/dictionaries/sunboss.xml
new file mode 100644
index 0000000..c281e18
--- /dev/null
+++ b/.idea/dictionaries/sunboss.xml
@@ -0,0 +1,7 @@
+
+
+
+ rgbw
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index fe72da5..21c27e8 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -10,6 +10,7 @@
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..ad1a3c2
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5d19981..635999d 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,8 +1,5 @@
-
-
-
@@ -27,17 +24,7 @@
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index c480f6a..f396e7d 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -4,6 +4,7 @@
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
index 612e9ba..ea72fbd 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,2 +1,2 @@
/build
-/src/main/java/com/umarbhutta/xlightcompanion/particle/CloudAccount.java
+/src/main/java/ca/xlight/demoapp/SDK/CloudAccount.java
diff --git a/app/build.gradle b/app/build.gradle
index 336f42b..b69a53d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 23
- buildToolsVersion "23.0.3"
+ compileSdkVersion 25
+ buildToolsVersion '25.0.2'
defaultConfig {
- applicationId "com.umarbhutta.xlightcompanion"
+ applicationId "ca.xlight.demoapp"
minSdkVersion 19
- targetSdkVersion 23
- versionCode 1
- versionName "1.0"
+ targetSdkVersion 25
+ versionCode 2
+ versionName "2.0"
}
buildTypes {
release {
@@ -27,13 +27,26 @@ android {
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
- compile 'com.android.support:appcompat-v7:23.4.0'
- compile 'com.android.support:design:23.4.0'
- compile 'com.android.support:support-v4:23.4.0'
- compile 'com.android.support:recyclerview-v7:23.4.0'
+ compile 'com.android.support:appcompat-v7:25.2.0'
+ compile 'com.android.support:support-fragment:25.2.0'
+ compile 'com.android.support:design:25.2.0'
+ compile 'com.android.support:support-v4:25.2.0'
+ compile 'com.android.support:recyclerview-v7:25.2.0'
compile 'com.github.clans:fab:1.6.4'
+ compile 'com.yanzhenjie:recyclerview-swipe:1.1.3'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
- compile 'io.particle:cloudsdk:0.3.4'
+
+ // BY DEFAULT, BUILD APP AGAINST THE LOCAL SDK SOURCE
+ // (i.e.: make modifications to the SDK source in the local repo show up in this app
+ // just by rebuilding)
+ compile project(':cloudsdk')
+ //
+ // **OR**
+ //
+ // comment out the above, and
+ // UNCOMMENT THE FOLLOWING TO USE A PUBLISHED VERSION OF THE SDK:
+ //compile 'io.particle:cloudsdk:0.3.4'
+
compile 'io.particle:devicesetup:0.3.6'
compile 'me.priyesh:chroma:1.0.2'
}
diff --git a/app/src/androidTest/java/com/umarbhutta/xlightcompanion/ApplicationTest.java b/app/src/androidTest/java/ca/xlight/demoapp/ApplicationTest.java
similarity index 88%
rename from app/src/androidTest/java/com/umarbhutta/xlightcompanion/ApplicationTest.java
rename to app/src/androidTest/java/ca/xlight/demoapp/ApplicationTest.java
index 4ad3ee5..79bc4f0 100644
--- a/app/src/androidTest/java/com/umarbhutta/xlightcompanion/ApplicationTest.java
+++ b/app/src/androidTest/java/ca/xlight/demoapp/ApplicationTest.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion;
+package ca.xlight.demoapp;
import android.app.Application;
import android.test.ApplicationTestCase;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 101e6c2..c407bec 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,9 +1,15 @@
+ package="ca.xlight.demoapp">
+
+
+
+
+ android:screenOrientation="portrait" />
+ android:screenOrientation="portrait" />
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterDelegate.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterDelegate.java
new file mode 100644
index 0000000..0084587
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterDelegate.java
@@ -0,0 +1,45 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import java.util.Set;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public class BLEAdapterDelegate implements BLEAdapterWrapper {
+
+ private final BluetoothAdapter adapter;
+
+ public BLEAdapterDelegate(BluetoothAdapter adapter) {
+ assert adapter != null;
+ this.adapter = adapter;
+ }
+
+ @Override
+ public Set getBondedDevices() {
+ return adapter.getBondedDevices();
+ }
+
+ @Override
+ public void cancelDiscovery() {
+ adapter.cancelDiscovery();
+ }
+
+ @Override
+ public boolean isDiscovering() {
+ return adapter.isDiscovering();
+ }
+
+ @Override
+ public void startDiscovery() {
+ adapter.startDiscovery();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return adapter.isEnabled();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterFactory.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterFactory.java
new file mode 100644
index 0000000..30a16bb
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterFactory.java
@@ -0,0 +1,18 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothAdapter;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public class BLEAdapterFactory {
+ private BLEAdapterFactory() {
+ // utility class
+ }
+
+ public static BLEAdapterWrapper getBluetoothAdapterWrapper() {
+ BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
+ return defaultAdapter != null ? new BLEAdapterDelegate(defaultAdapter) : new NullBLEWrapper();
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterWrapper.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterWrapper.java
new file mode 100644
index 0000000..d261b27
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEAdapterWrapper.java
@@ -0,0 +1,22 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.Set;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public interface BLEAdapterWrapper {
+
+ Set getBondedDevices();
+
+ void cancelDiscovery();
+
+ boolean isDiscovering();
+
+ void startDiscovery();
+
+ boolean isEnabled();
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEBridge.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEBridge.java
new file mode 100644
index 0000000..cfb63d8
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEBridge.java
@@ -0,0 +1,310 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import ca.xlight.demoapp.SDK.BaseBridge;
+import ca.xlight.demoapp.SDK.SerialMessage;
+import ca.xlight.demoapp.SDK.xltDevice;
+
+
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+/**
+ * Created by sunboss on 2016-11-16.
+ */
+@SuppressWarnings({"UnusedDeclaration"})
+public class BLEBridge extends BaseBridge {
+ // misc
+ private static final String TAG = BLEBridge.class.getSimpleName();
+ private static final boolean D = true;
+
+ private DeviceConnector mDeviceConnector = new NullDeviceConnector();
+ private SerialMessage mMsg = new SerialMessage();
+ private boolean m_bPaired = false;
+ private boolean m_bLoggedIn = false;
+ private BluetoothDevice m_bleDevice = null;
+ private String m_bleAddress;
+ private static int resultCode;
+
+ public BLEBridge() {
+ super();
+ setName(TAG);
+ }
+
+ public boolean isPaired() {
+ return m_bPaired;
+ }
+
+ public boolean connectController() {
+ // Connect SmartController via BLE
+ if( m_bleDevice != null && m_bleAddress.length() > 0 ) {
+ MessageHandler messageHandler = new MessageHandlerImpl(mHandler);
+ mDeviceConnector = new BLEDeviceConnector(messageHandler, m_bleAddress);
+ mDeviceConnector.connect();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disconnectController() {
+ // Disconnect SmartController BLE
+ if( mDeviceConnector != null ) {
+ mDeviceConnector.disconnect();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean Login(final String key) {
+ // ToDo: send msg and wait result
+ // 0;139;3;1;3;AccessCode\n
+ //CharSequence chars;
+ //mDeviceConnector.sendAsciiMessage();
+ //m_bLoggedIn = true;
+ return m_bLoggedIn;
+ }
+
+ @Override
+ public void setName(final String name) {
+ super.setName(name);
+
+ // Retrieve Bluetooth Device by device name
+ m_bleDevice = BLEPairedDeviceList.SearchDeviceName(name);
+ if( m_bleDevice != null ) {
+ m_bPaired = (m_bleDevice.getBondState() == BOND_BONDED);
+ m_bleAddress = m_bleDevice.getAddress();
+ } else {
+ m_bPaired = false;
+ m_bleAddress = "";
+ }
+ }
+
+ public String getAddress() {
+ return m_bleAddress;
+ }
+
+ public int QueryStatus(final int nodeID) {
+ //1;139;1;1;12\n
+ String strParam = String.format("%d;%d;1;1;12", nodeID, xltDevice.NODEID_SMARTPHONE);
+ return AsynSendMessage(strParam);
+ }
+
+ public int PowerSwitch(final int nodeID, final int state) {
+ //1;139;1;1;7;0\n
+ //1;139;1;1;7;1\n
+ //1;139;1;1;7;2\n
+ String strParam = String.format("%d;%d;1;1;7;%d", nodeID, xltDevice.NODEID_SMARTPHONE, state);
+ return AsynSendMessage(strParam);
+ }
+
+ public int ChangeBrightness(final int nodeID, final int value) {
+ //1;139;1;1;9;55\n
+ String strParam = String.format("%d;%d;1;1;9;%d", nodeID, xltDevice.NODEID_SMARTPHONE, value);
+ return AsynSendMessage(strParam);
+ }
+
+ public int ChangeCCT(final int nodeID, final int value) {
+ //1;139;1;1;11;3800\n
+ String strParam = String.format("%d;%d;1;1;11;%d", nodeID, xltDevice.NODEID_SMARTPHONE, value);
+ return AsynSendMessage(strParam);
+ }
+
+ public int ChangeColor(final int nodeID, final int ring, final boolean state, final int br, final int ww, final int r, final int g, final int b) {
+ //1;139;1;1;13;70:0:255\n
+ String strParam = String.format("%d;%d;1;1;13;%d:%d:%d:%d:%d", nodeID, xltDevice.NODEID_SMARTPHONE, br, ww, r, g, b);
+ return AsynSendMessage(strParam);
+ }
+
+ public int ChangeScenario(final int nodeID, final int scenario) {
+ //1;139;1;1;15;1\n
+ String strParam = String.format("%d;%d;1;1;15;%d", nodeID, xltDevice.NODEID_SMARTPHONE, scenario);
+ return AsynSendMessage(strParam);
+ }
+
+ public int SetSpecialEffect(final int nodeID, final int filter) {
+ //1;139;1;1;17;1\n
+ String strParam = String.format("%d;%d;1;1;17;%d", nodeID, xltDevice.NODEID_SMARTPHONE, filter);
+ return AsynSendMessage(strParam);
+ }
+
+ public int SetRelayKey(final int nodeID, final boolean on_off, final String keys) {
+ //130;139;1;1;19;65:keys\n
+ String strParam = String.format("%d;%d;1;1;19;%d:%s", nodeID, xltDevice.NODEID_SMARTPHONE, keys);
+ return AsynSendMessage(strParam);
+ }
+
+ public int SysSetupWiFi(final String sSSID, final String sPassword, final int nAuth, final int nCipher) {
+ String strParam = String.format("0;%d;3;1;6;0:%s", xltDevice.NODEID_SMARTPHONE, sSSID);
+ if( sPassword.length() > 0 ) {
+ strParam += String.format(":%s", sPassword);
+ }
+ if( nAuth > 0 ) {
+ strParam += String.format(":%d", nAuth);
+ }
+ if( nCipher > 0 ) {
+ strParam += String.format(":%d", nCipher);
+ }
+ return AsynSendMessage(strParam);
+ }
+
+ public int SysQueryCoreID() {
+ String strParam = String.format("0;%d;3;1;6;1", xltDevice.NODEID_SMARTPHONE);
+ return AsynSendMessage(strParam);
+ }
+
+ public int SysConfig(final String sCmd) {
+ String strParam = String.format("0;%d;3;1;6;%s", xltDevice.NODEID_SMARTPHONE, sCmd);
+ return AsynSendMessage(strParam);
+ }
+
+ public int SysControl(final String sCmd) {
+ String strParam = String.format("0;%d;3;1;13;%s", xltDevice.NODEID_SMARTPHONE, sCmd);
+ return AsynSendMessage(strParam);
+ }
+
+ private int AsynSendMessage(final String strMsg) {
+ if( !isConnected() ) return -1;
+ new Thread() {
+ @Override
+ public void run() {
+ mDeviceConnector.sendAsciiMessage(strMsg);
+ resultCode = 1;
+ }
+ }.start();
+ return resultCode;
+ }
+
+ // The Handler that gets information back from the BluetoothService
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MessageHandler.MSG_CONNECTED:
+ // Device connected
+ Log.i(TAG, "onConnectSuccess");
+ setConnect(true);
+ //onBluetoothStateChanged();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.BLE, xltDevice.BCS_CONNECTED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.BLE, true);
+ }
+ break;
+ case MessageHandler.MSG_CONNECTING:
+ Log.i(TAG, "onConnecting");
+ setConnect(false);
+ //onBluetoothStateChanged();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.BLE, xltDevice.BCS_CONNECTING);
+ break;
+ case MessageHandler.MSG_NOT_CONNECTED:
+ Log.i(TAG, "onDisconnected");
+ setConnect(false);
+ //onBluetoothStateChanged();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.BLE, xltDevice.BCS_NOT_CONNECTED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.BLE, false);
+ }
+ break;
+ case MessageHandler.MSG_CONNECTION_FAILED:
+ Log.w(TAG, "onConnectFailed");
+ setConnect(false);
+ //onBluetoothStateChanged();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.BLE, xltDevice.BCS_CONNECTION_FAILED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.BLE, false);
+ }
+ break;
+ case MessageHandler.MSG_CONNECTION_LOST:
+ Log.w(TAG, "onConnectionLost");
+ setConnect(false);
+ //onBluetoothStateChanged();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.BLE, xltDevice.BCS_CONNECTION_LOST);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.BLE, false);
+ }
+ break;
+ case MessageHandler.MSG_BYTES_WRITTEN:
+ String written = new String((byte[]) msg.obj);
+ Log.i(TAG, "written = '" + written + "'");
+ break;
+ case MessageHandler.MSG_LINE_READ:
+ String line = (String) msg.obj;
+ if (D) Log.d(TAG, line);
+ // Parse message
+ if( mMsg.parseString(line) ) {
+ if( mMsg.m_dest == xltDevice.NODEID_SMARTPHONE && mMsg.m_orig == xltDevice.NODEID_GATEWAY ) {
+ // Notification
+ if( mMsg.m_cmd == SerialMessage.C_PRESENTATION ) {
+ Bundle bdlEventData = new Bundle();
+ switch( mMsg.m_type ) {
+ case 1: // Alert
+ // ToDo: parse alarm message and Send alarm message
+ //...
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciAlarm));
+ }
+ break;
+ case 2: // Sensor Data
+ if( m_eventParser.ParseSensorDataEvent(mMsg.m_payload, bdlEventData) > 0 ) {
+ if( m_parentDevice.getEnableEventSendMessage() ) {
+ m_parentDevice.sendSensorDataMessage(bdlEventData);
+ }
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciSensorData));
+ }
+ }
+ break;
+ case 3: // Log
+ // ToDo:
+ break;
+ case 4: // Device Status
+ int nodeId = m_eventParser.ParseDeviceStatusEvent(mMsg.m_payload, bdlEventData);
+ if( nodeId > 0 ) {
+ if( m_parentDevice.getEnableEventSendMessage() ) {
+ m_parentDevice.sendDeviceStatusMessage(bdlEventData);
+ }
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ Intent devStatus = new Intent(xltDevice.bciDeviceStatus);
+ devStatus.putExtra("nd", nodeId);
+ m_parentContext.sendBroadcast(devStatus);
+ }
+ }
+ break;
+ case 5: // Device Config
+ // ToDo: parse 3 formats and send device config message
+ //...
+ if (m_parentDevice.getEnableEventBroadcast()) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciDeviceConfig));
+ }
+ break;
+ }
+ } else if( mMsg.m_cmd == SerialMessage.C_INTERNAL && mMsg.m_ack == 2 ) {
+ // System command ack
+ int result = 1;
+ if( mMsg.m_payload.length() > 0 ) {
+ if (mMsg.m_payload.charAt(0) == '0') result = 0;
+ }
+ m_parentDevice.onBridgeFunctionAck(result, mMsg.m_type, mMsg.m_payload);
+ // Core ID
+ if( mMsg.m_type == SerialMessage.I_CONFIG && result == 1 ) {
+ if( mMsg.m_payload.length() > 10 ) {
+ String[] attributes = mMsg.m_payload.split(":");
+ if( attributes.length == 3 ) {
+ if (attributes[1] == "0" || attributes[1] == "1") {
+ m_parentDevice.onBridgeCoreID(attributes[2]);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ };
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEDeviceConnector.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEDeviceConnector.java
new file mode 100644
index 0000000..bfe0f5a
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEDeviceConnector.java
@@ -0,0 +1,354 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * This class does all the work for setting up and managing Bluetooth
+ * connections with other devices. It has a thread that listens for
+ * incoming connections, a thread for connecting with a device, and a
+ * thread for performing data transmissions when connected.
+ */
+public class BLEDeviceConnector implements DeviceConnector {
+ private static final String TAG = BLEDeviceConnector.class.getSimpleName();
+ private static final boolean D = true;
+
+ public static final int CHANNEL = 1;
+
+ private final BluetoothAdapter mAdapter;
+ private final MessageHandler mHandler;
+ private final String mAddress;
+ private ConnectThread mConnectThread;
+ private ConnectedThread mConnectedThread;
+ private int mState;
+
+ @Override
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Prepare a new Bluetooth session.
+ *
+ * @param handler A Handler to send messages back to the UI Activity
+ */
+ public BLEDeviceConnector(MessageHandler handler, String address) {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mState = STATE_NONE;
+ mHandler = handler;
+ mAddress = address;
+ }
+
+ /**
+ * Set the current state of the connection
+ *
+ * @param state An integer defining the current connection state
+ */
+ private synchronized void setState(int state) {
+ if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
+ mState = state;
+ }
+
+ private BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
+
+ @Override
+ public synchronized void connect() {
+ BluetoothDevice device = getBluetoothAdapter().getRemoteDevice(mAddress);
+ connect(device);
+ }
+
+ /**
+ * Start the ConnectThread to initiate a connection to a remote device.
+ *
+ * @param device The BluetoothDevice to connect
+ */
+ public synchronized void connect(BluetoothDevice device) {
+ if (D) Log.d(TAG, "connect to: " + device);
+
+ // Cancel any thread attempting to make a connection
+ if (mState == STATE_CONNECTING) {
+ if (mConnectThread != null) {
+ mConnectThread.cancel();
+ mConnectThread = null;
+ }
+ }
+
+ // Cancel any thread currently running a connection
+ if (mConnectedThread != null) {
+ mConnectedThread.cancel();
+ mConnectedThread = null;
+ }
+
+ // Start the thread to connect with the given device
+ try {
+ mConnectThread = new ConnectThread(device);
+ mConnectThread.start();
+ setState(STATE_CONNECTING);
+ mHandler.sendConnectingTo(device.getName());
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Start the ConnectedThread to begin managing a Bluetooth connection
+ *
+ * @param socket The BluetoothSocket on which the connection was made
+ * @param device The BluetoothDevice that has been connected
+ */
+ public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
+ if (D) Log.d(TAG, "connected");
+
+ // Cancel the thread that completed the connection
+ if (mConnectThread != null) {
+ mConnectThread.cancel();
+ mConnectThread = null;
+ }
+
+ // Cancel any thread currently running a connection
+ if (mConnectedThread != null) {
+ mConnectedThread.cancel();
+ mConnectedThread = null;
+ }
+
+ // Start the thread to manage the connection and perform transmissions
+ mConnectedThread = new ConnectedThread(socket);
+ mConnectedThread.start();
+
+ setState(STATE_CONNECTED);
+ mHandler.sendConnectedTo(device.getName());
+ }
+
+ /**
+ * Stop all threads
+ */
+ @Override
+ public synchronized void disconnect() {
+ if (D) Log.d(TAG, "shutdown");
+
+ if (mConnectThread != null) {
+ mConnectThread.cancel();
+ mConnectThread = null;
+ }
+
+ if (mConnectedThread != null) {
+ mConnectedThread.shutdown();
+ mConnectedThread.cancel();
+ mConnectedThread = null;
+ }
+
+ setState(STATE_NONE);
+ mHandler.sendNotConnected();
+ }
+
+ @Override
+ public void sendAsciiMessage(CharSequence chars) {
+ write((chars.toString() + "\n").getBytes());
+ }
+
+ /**
+ * Write to the ConnectedThread in an unsynchronized manner
+ *
+ * @param out The bytes to write
+ * @see ConnectedThread#write(byte[])
+ */
+ private void write(byte[] out) {
+ // Create temporary object
+ ConnectedThread r;
+ // Synchronize a copy of the ConnectedThread
+ synchronized (this) {
+ if (mState != STATE_CONNECTED) return;
+ r = mConnectedThread;
+ }
+ // Perform the write unsynchronized
+ r.write(out);
+ }
+
+ /**
+ * Indicate that the connection attempt failed and notify the UI Activity.
+ */
+ private void connectionFailed() {
+ setState(STATE_NONE);
+ mHandler.sendConnectionFailed();
+ }
+
+ /**
+ * Indicate that the connection was lost and notify the UI Activity.
+ */
+ private void connectionLost() {
+ setState(STATE_NONE);
+ mHandler.sendConnectionLost();
+ }
+
+
+ /**
+ * This thread runs while attempting to make an outgoing connection
+ * with a device. It runs straight through; the connection either
+ * succeeds or fails.
+ */
+ private class ConnectThread extends Thread {
+ private final BluetoothSocket mmSocket;
+ private final BluetoothDevice mmDevice;
+
+ public ConnectThread(BluetoothDevice device) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ mmDevice = device;
+ BluetoothSocket tmp = null;
+
+ Log.i(TAG, "calling device.createRfcommSocket with channel " + CHANNEL + " ...");
+ try {
+ // call hidden method, see BluetoothDevice source code for more details:
+ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/bluetooth/BluetoothDevice.java
+ Method m = device.getClass().getMethod("createRfcommSocket", int.class);
+ tmp = (BluetoothSocket) m.invoke(device, CHANNEL);
+ Log.i(TAG, "setting socket to result of createRfcommSocket");
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ mmSocket = tmp;
+ }
+
+ public void run() {
+ Log.i(TAG, "BEGIN mConnectThread");
+ setName("ConnectThread");
+
+ // Always cancel discovery because it will slow down a connection
+ mAdapter.cancelDiscovery();
+
+ // Make a connection to the BluetoothSocket
+ try {
+ // This is a blocking call and will only return on a
+ // successful connection or an exception
+ mmSocket.connect();
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ connectionFailed();
+ try {
+ mmSocket.close();
+ } catch (IOException e2) {
+ Log.e(TAG, "unable to close() socket during connection failure", e2);
+ }
+ return;
+ }
+
+ // Reset the ConnectThread because we're done
+ synchronized (BLEDeviceConnector.this) {
+ mConnectThread = null;
+ }
+
+ // Start the connected thread
+ connected(mmSocket, mmDevice);
+ }
+
+ public void cancel() {
+ try {
+ mmSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "close() of connect socket failed", e);
+ }
+ }
+ }
+
+ /**
+ * This thread runs during a connection with a remote device.
+ * It handles all incoming and outgoing transmissions.
+ */
+ private class ConnectedThread extends Thread {
+ private final BluetoothSocket mmSocket;
+ private final InputStream mmInStream;
+ private final OutputStream mmOutStream;
+
+ public ConnectedThread(BluetoothSocket socket) {
+ Log.d(TAG, "create ConnectedThread");
+ mmSocket = socket;
+ InputStream tmpIn = null;
+ OutputStream tmpOut = null;
+
+ // Get the BluetoothSocket input and output streams
+ try {
+ tmpIn = socket.getInputStream();
+ tmpOut = socket.getOutputStream();
+ } catch (IOException e) {
+ Log.e(TAG, "temp sockets not created", e);
+ }
+
+ mmInStream = tmpIn;
+ mmOutStream = tmpOut;
+ }
+
+ private boolean stop = false;
+ private boolean hasReadAnything = false;
+
+ public void shutdown() {
+ stop = true;
+ if (!hasReadAnything) return;
+ if (mmInStream != null) {
+ try {
+ mmInStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "close() of InputStream failed.");
+ }
+ }
+ }
+
+ public void run() {
+ Log.i(TAG, "BEGIN mConnectedThread");
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(mmInStream));
+
+ while (!stop) {
+ try {
+ String line = reader.readLine();
+ if (line != null) {
+ mHandler.sendLineRead(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "disconnected", e);
+ connectionLost();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Write to the connected OutStream.
+ *
+ * @param bytes The bytes to write
+ */
+ public void write(byte[] bytes) {
+ try {
+ mmOutStream.write(bytes);
+ mHandler.sendBytesWritten(bytes);
+ } catch (IOException e) {
+ Log.e(TAG, "Exception during write", e);
+ }
+ }
+
+ public void cancel() {
+ try {
+ mmSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "close() of connect socket failed", e);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEPairedDeviceList.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEPairedDeviceList.java
new file mode 100644
index 0000000..5031b91
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/BLEPairedDeviceList.java
@@ -0,0 +1,78 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Created by sunboss on 2016-12-10.
+ */
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BLEPairedDeviceList {
+ // misc
+ private static final String TAG = BLEPairedDeviceList.class.getSimpleName();
+
+ public static final int REQUEST_ENABLE_BT = 1010;
+ public static final String XLIGHT_BLE_NAME_PREFIX = "Xlight";
+ //private static final int XLIGHT_BLE_CLASS = 0x9A050C; // default value for HC-06 is 0x1F00
+ private static final int XLIGHT_BLE_CLASS = 0x1F00; // default value for HC-06 is 0x1F00
+
+ private static BLEAdapterWrapper mBtAdapter = BLEAdapterFactory.getBluetoothAdapterWrapper();
+ private static ArrayList mPairedDevices = new ArrayList<>();
+
+ private static boolean m_bInitialized = false;
+ private static Context m_Context;
+ private static boolean m_bSupported = false;
+
+ public static void init(Context context) {
+ m_Context = context;
+ m_bSupported = m_Context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ if (!m_bSupported) {
+ Log.e(TAG, "Bluetooth NOT supported!");
+ return;
+ }
+
+ CheckBluetoothState();
+ m_bInitialized = true;
+ }
+
+ public static boolean initialized() {
+ return m_bInitialized;
+ }
+
+ public static boolean IsSupported() {
+ return m_bSupported;
+ }
+
+ public static boolean IsEnabled() {
+ return mBtAdapter.isEnabled();
+ }
+
+ public static void CheckBluetoothState() {
+ if (IsEnabled()) {
+ Log.d(TAG, "Bluetooth is enabled...");
+ Set devices = mBtAdapter.getBondedDevices();
+ if (devices != null && !devices.isEmpty()) {
+ for (BluetoothDevice device : devices) {
+ if (device.getBluetoothClass().hashCode() == XLIGHT_BLE_CLASS && device.getName().startsWith(XLIGHT_BLE_NAME_PREFIX)) {
+ mPairedDevices.add(device);
+ }
+ }
+ }
+ }
+ }
+
+ public static BluetoothDevice SearchDeviceName(final String devName) {
+ for (BluetoothDevice device : mPairedDevices) {
+ if (device.getName().equalsIgnoreCase(devName)) {
+ return device;
+ }
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/DeviceConnector.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/DeviceConnector.java
new file mode 100644
index 0000000..bb935e0
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/DeviceConnector.java
@@ -0,0 +1,20 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public interface DeviceConnector {
+
+ int STATE_NONE = 0; // we're doing nothing
+ int STATE_CONNECTING = 2; // now initiating an outgoing connection
+ int STATE_CONNECTED = 3; // now connected to a remote device
+
+ void connect();
+
+ void disconnect();
+
+ void sendAsciiMessage(CharSequence chars);
+
+ int getState();
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandler.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandler.java
new file mode 100644
index 0000000..1864c8f
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandler.java
@@ -0,0 +1,31 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public interface MessageHandler {
+
+ int MSG_NOT_CONNECTED = 10;
+ int MSG_CONNECTING = 11;
+ int MSG_CONNECTED = 12;
+ int MSG_CONNECTION_FAILED = 13;
+ int MSG_CONNECTION_LOST = 14;
+ int MSG_LINE_READ = 21;
+ int MSG_BYTES_WRITTEN = 22;
+
+ void sendLineRead(String line);
+
+ void sendBytesWritten(byte[] bytes);
+
+ void sendConnectingTo(String deviceName);
+
+ void sendConnectedTo(String deviceName);
+
+ void sendNotConnected();
+
+ void sendConnectionFailed();
+
+ void sendConnectionLost();
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandlerImpl.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandlerImpl.java
new file mode 100644
index 0000000..ab24bba
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/MessageHandlerImpl.java
@@ -0,0 +1,58 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.os.Handler;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public class MessageHandlerImpl implements MessageHandler {
+ private final Handler handler;
+
+ public MessageHandlerImpl(Handler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ public void sendLineRead(String line) {
+ handler.obtainMessage(MSG_LINE_READ, -1, -1, line).sendToTarget();
+ }
+
+ @Override
+ public void sendBytesWritten(byte[] bytes) {
+ handler.obtainMessage(MSG_BYTES_WRITTEN, -1, -1, bytes).sendToTarget();
+ }
+
+ @Override
+ public void sendConnectingTo(String deviceName) {
+ sendMessage(MSG_CONNECTING, deviceName);
+ }
+
+ @Override
+ public void sendConnectedTo(String deviceName) {
+ sendMessage(MSG_CONNECTED, deviceName);
+ }
+
+ @Override
+ public void sendNotConnected() {
+ sendMessage(MSG_NOT_CONNECTED);
+ }
+
+ @Override
+ public void sendConnectionFailed() {
+ sendMessage(MSG_CONNECTION_FAILED);
+ }
+
+ @Override
+ public void sendConnectionLost() {
+ sendMessage(MSG_CONNECTION_LOST);
+ }
+
+ private void sendMessage(int messageId, String deviceName) {
+ handler.obtainMessage(messageId, -1, -1, deviceName).sendToTarget();
+ }
+
+ private void sendMessage(int messageId) {
+ handler.obtainMessage(messageId).sendToTarget();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullBLEWrapper.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullBLEWrapper.java
new file mode 100644
index 0000000..656a67e
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullBLEWrapper.java
@@ -0,0 +1,37 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public class NullBLEWrapper implements BLEAdapterWrapper {
+ @Override
+ public Set getBondedDevices() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void cancelDiscovery() {
+ // nothing to cancel
+ }
+
+ @Override
+ public boolean isDiscovering() {
+ return false;
+ }
+
+ @Override
+ public void startDiscovery() {
+ // nothing to discover
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullDeviceConnector.java b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullDeviceConnector.java
new file mode 100644
index 0000000..9ec35ec
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BLE/NullDeviceConnector.java
@@ -0,0 +1,27 @@
+package ca.xlight.demoapp.SDK.BLE;
+
+/**
+ * Created by sunboss on 2017-03-21.
+ */
+
+public class NullDeviceConnector implements DeviceConnector {
+ @Override
+ public void connect() {
+ // do nothing
+ }
+
+ @Override
+ public void disconnect() {
+ // do nothing
+ }
+
+ @Override
+ public void sendAsciiMessage(CharSequence chars) {
+ // do nothing
+ }
+
+ @Override
+ public int getState() {
+ return STATE_NONE;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/BaseBridge.java b/app/src/main/java/ca/xlight/demoapp/SDK/BaseBridge.java
new file mode 100644
index 0000000..f64922b
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/BaseBridge.java
@@ -0,0 +1,61 @@
+package ca.xlight.demoapp.SDK;
+
+/**
+ * Created by sunboss on 2016-11-17.
+ */
+
+import android.content.Context;
+
+@SuppressWarnings({"UnusedDeclaration"})
+// Base Class for Bridges
+public class BaseBridge {
+ private boolean m_bConnected = false;
+ private String m_Name = "Unknown bridge";
+ private int m_priority = 5; // the bigger, the higher
+ protected Context m_parentContext = null;
+ protected xltDevice m_parentDevice = null;
+ protected EventParser m_eventParser = new EventParser();
+
+
+ public boolean isConnected() {
+ return m_bConnected;
+ }
+
+ public void setConnect(final boolean connected) {
+ m_bConnected = connected;
+ }
+
+ public int getNodeID() {
+ if( m_parentDevice != null ) {
+ return m_parentDevice.getDeviceID();
+ } else {
+ return xltDevice.DEFAULT_DEVICE_ID;
+ }
+ }
+
+ public String getName() {
+ return m_Name;
+ }
+
+ public void setName(final String name) {
+ m_Name = name;
+ m_eventParser.SetTag(name);
+ }
+
+ public int getPriority() {
+ return m_priority;
+ }
+
+ public void setPriority(final int priority) {
+ m_priority = priority;
+ }
+
+ public void setParentContext(Context context) {
+ m_parentContext = context;
+ }
+
+ public void setParentDevice(xltDevice device) {
+ m_parentDevice = device;
+ m_eventParser.SetParentDevice(device);
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/CloudBridge.java b/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/CloudBridge.java
new file mode 100644
index 0000000..78a3a05
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/CloudBridge.java
@@ -0,0 +1,512 @@
+package ca.xlight.demoapp.SDK.Cloud;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import ca.xlight.demoapp.SDK.BaseBridge;
+import ca.xlight.demoapp.SDK.xltDevice;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import io.particle.android.sdk.cloud.ParticleCloudException;
+import io.particle.android.sdk.cloud.ParticleCloudSDK;
+import io.particle.android.sdk.cloud.ParticleDevice;
+import io.particle.android.sdk.cloud.ParticleEvent;
+import io.particle.android.sdk.cloud.ParticleEventHandler;
+
+/**
+ * Created by sunboss on 2016-11-23.
+ */
+@SuppressWarnings({"UnusedDeclaration"})
+public class CloudBridge extends BaseBridge {
+ // misc
+ private static final String TAG = CloudBridge.class.getSimpleName();
+
+ private ParticleDevice currDevice;
+ private static int resultCode;
+ private static long subscriptionId = 0;
+
+ public CloudBridge() {
+ super();
+ setName(TAG);
+ }
+
+ public boolean connectCloud(final String devID) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ currDevice = ParticleCloudSDK.getCloud().getDevice(devID);
+ SubscribeDeviceEvents();
+ setConnect(true);
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.Cloud, xltDevice.BCS_CONNECTED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.Cloud, true);
+ }
+
+ // Delay 2 seconds, then Query Main Device
+ Handler myHandler = new Handler(Looper.getMainLooper());
+ myHandler.postDelayed(new Runnable() {
+ @Override
+ public void run()
+ {
+ JSONCommandQueryDevice(0);
+ }
+ }, 2000);
+
+ } catch (ParticleCloudException e) {
+ e.printStackTrace();
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.Cloud, xltDevice.BCS_CONNECTION_FAILED);
+ }
+ }
+ }).start();
+
+ return true;
+ }
+
+ public boolean disconnectCloud() {
+ UnsubscribeDeviceEvents();
+ setConnect(false);
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.Cloud, xltDevice.BCS_NOT_CONNECTED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.Cloud, false);
+ }
+ return true;
+ }
+
+ public int JSONCommandPower(final int nodeID, final boolean state) {
+ new Thread() {
+ @Override
+ public void run() {
+ int power = state ? xltDevice.STATE_ON : xltDevice.STATE_OFF;
+
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_POWER + ",\"nd\":" + nodeID + ",\"state\":" + power + "}";
+ //String json = "{'cmd':" + VALUE_POWER + ",'nd':" + nodeId + ",'state':" + power + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.e(TAG, "JSONCommandPower" + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandBrightness(final int nodeID, final int value) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_BRIGHTNESS + ",\"nd\":" + nodeID + ",\"value\":" + value + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.e(TAG, "JSONCommandBrightness" + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandCCT(final int nodeID, final int value) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_CCT + ",\"nd\":" + nodeID + ",\"value\":" + value + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.d(TAG, "JSONCommandCCT" + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandColor(final int nodeID, final int ring, final boolean state, final int br, final int ww, final int r, final int g, final int b) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ int power = state ? 1 : 0;
+
+ String json = "{\"cmd\":" + xltDevice.CMD_COLOR + ",\"nd\":" + nodeID + ",\"ring\":[" + ring + "," + power + "," + br + "," + ww + "," + r + "," + g + "," + b + "]}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.e(TAG, "JSONCommandColor " + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+
+ public int JSONCommandScenario(final int nodeID, final int scenario) {
+ new Thread() {
+ @Override
+ public void run() {
+ //position corresponds to the spinner in Control. position of 1 corresponds to s1, 2 to s2. The 0th index in the spinner is the "None" item,
+ //hence the parameter of position is good to go in this function as is - doesn't need to be incremented by 1 for the uid for scenario
+
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_SCENARIO + ",\"nd\":" + nodeID + ",\"SNT_id\":" + scenario + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.e(TAG, "JSONCommandScenario " + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandSpecialEffect(final int nodeID, final int filter) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_EFFECT + ",\"nd\":" + nodeID + ",\"filter\":" + filter + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.e(TAG, "JSONCommandSpecialEffect " + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandQueryDevice(final int nodeID) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_QUERY + ",\"nd\":" + (nodeID == 0 ? getNodeID() : nodeID) + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.i(TAG, "JSONCommandQueryDevice" + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONCommandRelaySwitch(final int nodeID, final boolean on_off, final String keys) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String json = "{\"cmd\":" + xltDevice.CMD_EXT + ",\"nd\":" + nodeID + ",\"msg\":1" + ",\"ack\":1" + ",\"tag\":" + (on_off ? 65 : 66) + ",\"pl\":\"" + keys + "\"}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ try {
+ Log.d(TAG, "JSONCommandRelaySwitch" + message.get(0));
+ resultCode = currDevice.callFunction("JSONCommand", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONConfigScenario(final int scenarioId, final int brightness, final int cw, final int ww, final int r, final int g, final int b, final int filter) {
+ new Thread() {
+ @Override
+ public void run() {
+ boolean x[] = {false, false, false};
+
+ //construct first part of string input, and store it in arraylist (of size 1)
+ String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"s" + scenarioId + "\",\"ring1\":" + " '}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ //send in first part of string
+ try {
+ Log.e(TAG, "JSONConfigScenario " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[0] = true;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+
+ if (x[0]) {
+ //construct second part of string input, store in arraylist
+ json = "{'x1': '[" + xltDevice.STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"ring2\":[" + xltDevice.STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "], '}";
+ message.add(json);
+ //send in second part of string
+ try {
+ Log.e(TAG, "JSONConfigScenario " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[1] = true;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+
+ if (x[1]) {
+ //construct last part of string input, store in arraylist
+ //json = "\"ring3\":[" + xltDevice.STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"brightness\":" + brightness + ",\"filter\":" + DEFAULT_FILTER_ID + "}";
+ json = "\"ring3\":[" + xltDevice.STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"brightness\":" + brightness + ",\"filter\":" + filter + "}";
+ message.add(json);
+ //send in last part of string
+ try {
+ Log.e(TAG, "JSONConfigScenario " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONConfigSchudle(final int scheduleId, final boolean isRepeat, final String weekdays, final int hour, final int minute, final int alarmId) {
+ final int[] doneSending = {0};
+ new Thread() {
+ @Override
+ public void run() {
+ boolean x[] = {false, false};
+
+ //SCHEDULE
+ int repeat = isRepeat ? 1 : 0;
+
+ //construct first part of string input, and store it in arraylist (of size 1)
+ String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"a" + scheduleId + "\",\"isRepeat\":" + "1" + ", '}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ //send in first part of string
+ try {
+ Log.e(TAG, "JSONConfigSchedule " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[0] = true;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+
+ if (x[0]) {
+ //construct second part of string input, store in arraylist
+ json = "\"weekdays\":" + "0" + ",\"hour\":" + hour + ",\"min\":" + minute + ",\"alarm_id\":" + alarmId + "}";
+ message.add(json);
+ //send in second part of string
+ try {
+ Log.e(TAG, "JSONConfigSchedule " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[1] = true;
+ doneSending[0] = 5;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONConfigRule(final int ruleId, final int scheduleId, final int scenarioId) {
+ new Thread() {
+ @Override
+ public void run() {
+ boolean x[] = {false, false};
+
+ //construct first part of string input, and store it in arraylist (of size 1)
+ String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"r" + ruleId + "\",\"nd\":" + getNodeID() + ", '}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ //send in first part of string
+ try {
+ Log.e(TAG, "JSONConfigRule" + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[0] = true;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+
+ if (x[0]) {
+ //construct second part of string input, store in arraylist
+ json = "\"SCT_uid\":\"a" + scheduleId + "\",\"SNT_uid\":\"s" + scenarioId + "\",\"notif_uid\":\"n" + ruleId + "\"}";
+ message.add(json);
+ //send in second part of string
+ try {
+ Log.i(TAG, "JSONConfigRule" + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ x[1] = true;
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int JSONGetDeviceStatus(final int nodeID) {
+ new Thread() {
+ @Override
+ public void run() {
+ //construct first part of string input, and store it in arraylist (of size 1)
+ String json = "{\"op\":0,\"fl\":1,\"run\":0,\"uid\":\"h" + nodeID + "}";
+ ArrayList message = new ArrayList<>();
+ message.add(json);
+ //send in first part of string
+ try {
+ Log.d(TAG, "JSONGetDeviceStatus " + message.get(0));
+ resultCode = currDevice.callFunction("JSONConfig", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ message.clear();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public int FastCallPowerSwitch(final int nodeID, final int state) {
+ new Thread() {
+ @Override
+ public void run() {
+ // Make the Particle call here
+ String strParam = String.format("%d:%d", nodeID, state);
+ ArrayList message = new ArrayList<>();
+ message.add(strParam);
+ try {
+ Log.d(TAG, "FastCallPowerSwitch: " + strParam);
+ resultCode = currDevice.callFunction("PowerSwitch", message);
+ } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ return resultCode;
+ }
+
+ // Particle events publishing & subscribing
+ public long SubscribeDeviceEvents() {
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ subscriptionId = currDevice.subscribeToEvents(null, new ParticleEventHandler() {
+ public void onEvent(String eventName, ParticleEvent event) {
+ Log.i(TAG, "Received event: " + eventName + " with payload: " + event.dataPayload);
+ // Notes: due to bug of SDK 0.3.4, the eventName is not correct
+ /// We work around by specifying eventName
+ /*
+ if( event.dataPayload.contains("DHTt") || event.dataPayload.contains("ALS") || event.dataPayload.contains("PIR") ) {
+ eventName = xltDevice.eventSensorData;
+ } else {
+ eventName = xltDevice.eventDeviceStatus;
+ }*/
+
+ if( m_parentDevice != null ) {
+ // Demo option: use handler & sendMessage to inform activities
+ // Parsing Event
+ Bundle bdlEventData = new Bundle();
+ if (eventName.equalsIgnoreCase(xltDevice.eventDeviceStatus)) {
+ int nodeId = m_eventParser.ParseDeviceStatusEvent(event.dataPayload, bdlEventData);
+ if( nodeId > 0 ) {
+ if( m_parentDevice.getEnableEventSendMessage() ) {
+ m_parentDevice.sendDeviceStatusMessage(bdlEventData);
+ }
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ Intent devStatus = new Intent(xltDevice.bciDeviceStatus);
+ devStatus.putExtra("nd", nodeId);
+ m_parentContext.sendBroadcast(devStatus);
+ }
+ }
+ } else if (eventName.equalsIgnoreCase(xltDevice.eventSensorData)) {
+ if( m_eventParser.ParseSensorDataEvent(event.dataPayload, bdlEventData) > 0 ) {
+ if( m_parentDevice.getEnableEventSendMessage() ) {
+ m_parentDevice.sendSensorDataMessage(bdlEventData);
+ }
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciSensorData));
+ }
+ }
+ } else if (eventName.equalsIgnoreCase(xltDevice.eventAlarm)) {
+ // ToDo: parse alarm message and Send alarm message
+ //...
+ if( m_parentDevice.getEnableEventBroadcast() ) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciAlarm));
+ }
+ } else if (eventName.equalsIgnoreCase(xltDevice.eventDeviceConfig)) {
+ // ToDo: parse 3 formats and send device config message
+ //...
+ if (m_parentDevice.getEnableEventBroadcast()) {
+ m_parentContext.sendBroadcast(new Intent(xltDevice.bciDeviceConfig));
+ }
+ }
+ }
+ }
+
+ public void onEventError(Exception e) {
+ Log.e(TAG, "Event error: ", e);
+ }
+ });
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ return subscriptionId;
+ }
+
+ public void UnsubscribeDeviceEvents() {
+ new Thread() {
+ @Override
+ public void run() {
+ if( subscriptionId > 0 ) {
+ try {
+ currDevice.unsubscribeFromEvents(subscriptionId);
+ } catch (ParticleCloudException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }.start();
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/ParticleAdapter.java b/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/ParticleAdapter.java
new file mode 100644
index 0000000..1f979f5
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/Cloud/ParticleAdapter.java
@@ -0,0 +1,108 @@
+package ca.xlight.demoapp.SDK.Cloud;
+
+import android.content.Context;
+
+import ca.xlight.demoapp.SDK.CloudAccount;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.particle.android.sdk.cloud.ParticleCloudException;
+import io.particle.android.sdk.cloud.ParticleCloudSDK;
+import io.particle.android.sdk.cloud.ParticleDevice;
+
+/**
+ * Created by Umar Bhutta.
+ */
+@SuppressWarnings({"UnusedDeclaration"})
+public class ParticleAdapter {
+ // misc
+ private static final String TAG = ParticleAdapter.class.getSimpleName();
+
+ private static boolean m_bInitialized = false;
+ private static int resultCode;
+ private static boolean m_bLoggedIn = false;
+ private static List m_devices;
+ private static ArrayList m_deviceID2Name = new ArrayList<>();
+
+ // Particle functions
+ public static void init(Context context) {
+ ParticleCloudSDK.init(context);
+ m_bInitialized = true;
+ }
+
+ public static boolean initialized() {
+ return m_bInitialized;
+ }
+
+ public static boolean isAuthenticated() {
+ //return (ParticleCloudSDK.getCloud().isLoggedIn());
+ return m_bLoggedIn;
+ }
+
+ public static void authenticate() {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ParticleCloudSDK.getCloud().logIn(CloudAccount.EMAIL, CloudAccount.PASSWORD);
+ queryDevices();
+ m_bLoggedIn = true;
+ } catch (ParticleCloudException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+
+ // Synchronous query
+ private static int queryDevices() {
+ // Make the Particle call here
+ try {
+ String sItem;
+ m_devices = ParticleCloudSDK.getCloud().getDevices();
+ m_deviceID2Name.clear();
+ for (ParticleDevice device : m_devices) {
+ sItem = device.getID() + ":" + device.getName();
+ m_deviceID2Name.add(sItem);
+ }
+ } catch (ParticleCloudException e) {
+ e.printStackTrace();
+ resultCode = -1;
+ }
+ return resultCode;
+ }
+
+ // Asynchronous operation
+ public static int getDeviceList() {
+ resultCode = 0;
+ new Thread() {
+ @Override
+ public void run() {
+ queryDevices();
+ }
+ }.start();
+ return resultCode;
+ }
+
+ public static int getDeviceCount() {
+ return m_devices.size();
+ }
+
+ // ID:Name
+ public static ArrayList getDeviceNames() {
+ return m_deviceID2Name;
+ }
+
+ public static boolean checkDeviceID(final String devID) {
+ if( isAuthenticated() ) {
+ for (ParticleDevice device : m_devices) {
+ if (devID.equalsIgnoreCase(device.getID())) {
+ return true;
+ }
+
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/EventParser.java b/app/src/main/java/ca/xlight/demoapp/SDK/EventParser.java
new file mode 100644
index 0000000..3e73a95
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/EventParser.java
@@ -0,0 +1,148 @@
+package ca.xlight.demoapp.SDK;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by sunboss on 2017-05-29.
+ */
+@SuppressWarnings({"UnusedDeclaration"})
+public class EventParser {
+ private xltDevice m_parentDevice = null;
+ private String m_Tag = EventParser.class.getSimpleName();
+
+ public void SetParentDevice(final xltDevice parent) {
+ m_parentDevice = parent;
+ }
+
+ public void SetTag(final String tag) {
+ m_Tag = tag;
+ }
+
+ public int ParseDeviceStatusEvent(final String dataPayload, Bundle bdlControl) {
+ if( m_parentDevice == null) return -1;
+ int nodeId = -1;
+ try {
+ JSONObject jObject = new JSONObject(dataPayload);
+ if (jObject.has("nd")) {
+ nodeId = jObject.getInt("nd");
+ int ringId = xltDevice.RING_ID_ALL;
+ if (nodeId == m_parentDevice.getDeviceID() || m_parentDevice.findNodeFromDeviceList(nodeId) >= 0) {
+ bdlControl.putInt("nd", nodeId);
+ if (jObject.has("up")) {
+ m_parentDevice.setNodeAlive(nodeId, jObject.getInt("up") > 0);
+ bdlControl.putInt("up", jObject.getInt("up"));
+ } else {
+ m_parentDevice.setNodeAlive(nodeId, true);
+ }
+ if (jObject.has("tp")) {
+ m_parentDevice.setDeviceType(nodeId, jObject.getInt("tp"));
+ bdlControl.putInt("type", jObject.getInt("tp"));
+ }
+ if (jObject.has("km")) {
+ m_parentDevice.setKMState(nodeId, jObject.getInt("km"));
+ bdlControl.putInt("km", jObject.getInt("km"));
+ }
+ if (jObject.has("k_on")) {
+ m_parentDevice.updateKMState(nodeId, jObject.getString("k_on"), true);
+ bdlControl.putString("k_on", jObject.getString("k_on"));
+ }
+ if (jObject.has("k_off")) {
+ m_parentDevice.updateKMState(nodeId, jObject.getString("k_off"), false);
+ bdlControl.putString("k_off", jObject.getString("k_off"));
+ }
+ if (jObject.has("filter")) {
+ m_parentDevice.setFilter(nodeId, jObject.getInt("filter"));
+ bdlControl.putInt("filter", jObject.getInt("filter"));
+ }
+ if (jObject.has("Ring")) {
+ ringId = jObject.getInt("Ring");
+ }
+ bdlControl.putInt("Ring", ringId);
+ if (jObject.has("State")) {
+ m_parentDevice.setState(nodeId, jObject.getInt("State"));
+ bdlControl.putInt("State", jObject.getInt("State"));
+ }
+ if (jObject.has("BR")) {
+ m_parentDevice.setBrightness(nodeId, jObject.getInt("BR"));
+ bdlControl.putInt("BR", jObject.getInt("BR"));
+ }
+ if (jObject.has("CCT")) {
+ m_parentDevice.setCCT(nodeId, jObject.getInt("CCT"));
+ bdlControl.putInt("CCT", jObject.getInt("CCT"));
+ }
+ if (jObject.has("W")) {
+ m_parentDevice.setWhite(nodeId, ringId, jObject.getInt("W"));
+ bdlControl.putInt("W", jObject.getInt("W"));
+ }
+ if (jObject.has("R")) {
+ m_parentDevice.setRed(nodeId, ringId, jObject.getInt("R"));
+ bdlControl.putInt("R", jObject.getInt("R"));
+ }
+ if (jObject.has("G")) {
+ m_parentDevice.setGreen(nodeId, ringId, jObject.getInt("G"));
+ bdlControl.putInt("G", jObject.getInt("G"));
+ }
+ if (jObject.has("B")) {
+ m_parentDevice.setBlue(nodeId, ringId, jObject.getInt("B"));
+ bdlControl.putInt("B", jObject.getInt("B"));
+ }
+ }
+ }
+ } catch (final JSONException e) {
+ Log.e(m_Tag, "Json ParseDeviceStatusEvent error: " + e.getMessage());
+ return -1;
+ }
+ return nodeId;
+ }
+
+ public int ParseSensorDataEvent(final String dataPayload, Bundle bdlData) {
+ if( m_parentDevice == null) return -1;
+ try {
+ JSONObject jObject = new JSONObject(dataPayload);
+ if (jObject.has("DHTt")) {
+ m_parentDevice.m_Data.m_RoomTemp = jObject.getInt("DHTt");
+ bdlData.putInt("DHTt", (int)m_parentDevice.m_Data.m_RoomTemp);
+ }
+ if (jObject.has("DHTh")) {
+ m_parentDevice.m_Data.m_RoomHumidity = jObject.getInt("DHTh");
+ bdlData.putInt("DHTh", m_parentDevice.m_Data.m_RoomHumidity);
+ }
+ if (jObject.has("ALS")) {
+ m_parentDevice.m_Data.m_RoomBrightness = jObject.getInt("ALS");
+ bdlData.putInt("ALS", m_parentDevice.m_Data.m_RoomBrightness);
+ }
+ if (jObject.has("MIC")) {
+ m_parentDevice.m_Data.m_Mic = jObject.getInt("MIC");
+ bdlData.putInt("MIC", m_parentDevice.m_Data.m_Mic);
+ }
+ if (jObject.has("PIR")) {
+ m_parentDevice.m_Data.m_PIR = jObject.getInt("PIR");
+ bdlData.putInt("PIR", m_parentDevice.m_Data.m_PIR);
+ }
+ if (jObject.has("GAS")) {
+ m_parentDevice.m_Data.m_GAS = jObject.getInt("GAS");
+ bdlData.putInt("GAS", m_parentDevice.m_Data.m_GAS);
+ }
+ if (jObject.has("SMK")) {
+ m_parentDevice.m_Data.m_Smoke = jObject.getInt("SMK");
+ bdlData.putInt("SMK", m_parentDevice.m_Data.m_Smoke);
+ }
+ if (jObject.has("PM25")) {
+ m_parentDevice.m_Data.m_PM25 = jObject.getInt("PM25");
+ bdlData.putInt("PM25", m_parentDevice.m_Data.m_PM25);
+ }
+ if (jObject.has("NOS")) {
+ m_parentDevice.m_Data.m_Noise = jObject.getInt("NOS");
+ bdlData.putInt("NOS", m_parentDevice.m_Data.m_Noise);
+ }
+ } catch (final JSONException e) {
+ Log.e(m_Tag, "Json ParseSensorDataEvent error: " + e.getMessage());
+ return -1;
+ }
+ return 1;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/LAN/LANBridge.java b/app/src/main/java/ca/xlight/demoapp/SDK/LAN/LANBridge.java
new file mode 100644
index 0000000..f3272cf
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/LAN/LANBridge.java
@@ -0,0 +1,39 @@
+package ca.xlight.demoapp.SDK.LAN;
+
+import ca.xlight.demoapp.SDK.BaseBridge;
+import ca.xlight.demoapp.SDK.xltDevice;
+
+/**
+ * Created by sunboss on 2016-11-16.
+ */
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LANBridge extends BaseBridge {
+ // misc
+ private static final String TAG = LANBridge.class.getSimpleName();
+
+ public LANBridge() {
+ super();
+ setName(TAG);
+ }
+
+ public boolean connectController(final String address, final int port) {
+ // ToDo: connect to SmartController HTTP
+ //setConnect(true);
+ //m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.LAN, xltDevice.BCS_CONNECTED);
+ //if( m_parentDevice.m_onConnected != null ) {
+ // m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.LAN, true);
+ //}
+ return isConnected();
+ }
+
+ public boolean disconnectController() {
+ // ToDo: disconnect from SmartController
+ setConnect(false);
+ m_parentDevice.onBridgeStatusChanged(xltDevice.BridgeType.LAN, xltDevice.BCS_NOT_CONNECTED);
+ if( m_parentDevice.m_onConnected != null ) {
+ m_parentDevice.m_onConnected.onConnected(xltDevice.BridgeType.LAN, false);
+ }
+ return isConnected();
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/SerialMessage.java b/app/src/main/java/ca/xlight/demoapp/SDK/SerialMessage.java
new file mode 100644
index 0000000..1f77421
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/SerialMessage.java
@@ -0,0 +1,185 @@
+package ca.xlight.demoapp.SDK;
+
+/**
+ * Created by sunboss on 2016-12-16.
+ */
+
+// MySensors Serial Protocol, refer to https://www.mysensors.org/download/serial_api_20
+// Message format:
+// node-id;child-sensor-id;message-type;ack;sub-type;payload\n
+@SuppressWarnings({"UnusedDeclaration"})
+public class SerialMessage {
+
+ //-------------------------------------------------------------------------
+ // Constants
+ //-------------------------------------------------------------------------
+ // Message types
+ public static final int C_PRESENTATION = 0;
+ public static final int C_SET = 1;
+ public static final int C_REQ = 2;
+ public static final int C_INTERNAL = 3;
+ public static final int C_STREAM = 4; // For Firmware and other larger chunks of data that need to be divided into pieces.
+
+ // Type of sensor (used when presenting sensors)
+ public static final int S_DOOR = 0; // Door sensor, V_TRIPPED, V_ARMED
+ public static final int S_MOTION = 1; // Motion sensor, V_TRIPPED, V_ARMED
+ public static final int S_SMOKE = 2; // Smoke sensor, V_TRIPPED, V_ARMED
+ public static final int S_LIGHT = 3; // Binary light or relay, V_STATUS (or V_LIGHT), V_WATT
+ public static final int S_BINARY = 3; // Binary light or relay, V_STATUS (or V_LIGHT), V_WATT (same as S_LIGHT)
+ public static final int S_DIMMER = 4; // Dimmable light or fan device, V_STATUS (on/off), V_DIMMER (dimmer level 0-100), V_WATT
+ public static final int S_COVER = 5; // Blinds or window cover, V_UP, V_DOWN, V_STOP, V_DIMMER (open/close to a percentage)
+ public static final int S_TEMP = 6; // Temperature sensor, V_TEMP
+ public static final int S_HUM = 7; // Humidity sensor, V_HUM
+ public static final int S_BARO = 8; // Barometer sensor, V_PRESSURE, V_FORECAST
+ public static final int S_WIND = 9; // Wind sensor, V_WIND, V_GUST
+ public static final int S_RAIN = 10; // Rain sensor, V_RAIN, V_RAINRATE
+ public static final int S_UV = 11; // Uv sensor, V_UV
+ public static final int S_WEIGHT = 12; // Personal scale sensor, V_WEIGHT, V_IMPEDANCE
+ public static final int S_POWER = 13; // Power meter, V_WATT, V_KWH
+ public static final int S_HEATER = 14; // Header device, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP
+ public static final int S_DISTANCE = 15; // Distance sensor, V_DISTANCE
+ public static final int S_LIGHT_LEVEL = 16; // Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux)
+ public static final int S_ARDUINO_NODE = 17; // Used (internally) for presenting a non-repeating Arduino node
+ public static final int S_ARDUINO_REPEATER_NODE = 18; // Used (internally) for presenting a repeating Arduino node
+ public static final int S_LOCK = 19; // Lock device, V_LOCK_STATUS
+ public static final int S_IR = 20; // Ir device, V_IR_SEND, V_IR_RECEIVE
+ public static final int S_WATER = 21; // Water meter, V_FLOW, V_VOLUME
+ public static final int S_AIR_QUALITY = 22; // Air quality sensor, V_LEVEL
+ public static final int S_CUSTOM = 23; // Custom sensor
+ public static final int S_DUST = 24; // Dust sensor, V_LEVEL
+ public static final int S_SCENE_CONTROLLER = 25; // Scene controller device, V_SCENE_ON, V_SCENE_OFF.
+ public static final int S_RGB_LIGHT = 26; // RGB light. Send color component data using V_RGB. Also supports V_WATT
+ public static final int S_RGBW_LIGHT = 27; // RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT
+ public static final int S_COLOR_SENSOR = 28; // Color sensor, send color information using V_RGB
+ public static final int S_HVAC = 29; // Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE, V_TEMP
+ public static final int S_MULTIMETER = 30; // Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE
+ public static final int S_SPRINKLER = 31; // Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device)
+ public static final int S_WATER_LEAK = 32; // Water leak sensor, V_TRIPPED, V_ARMED
+ public static final int S_SOUND = 33; // Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB)
+ public static final int S_VIBRATION = 34; // Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz)
+ public static final int S_MOISTURE = 35; // Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?)
+
+ // Type of sensor data (for set/req/ack messages)
+ public static final int V_TEMP = 0; // S_TEMP. Temperature S_TEMP, S_HEATER, S_HVAC
+ public static final int V_HUM = 1; // S_HUM. Humidity
+ public static final int V_STATUS = 2; // S_LIGHT, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off
+ public static final int V_LIGHT = 2; // Same as V_STATUS
+ public static final int V_PERCENTAGE = 3; // S_DIMMER. Used for sending a percentage value 0-100 (%).
+ public static final int V_DIMMER = 3; // S_DIMMER. Same as V_PERCENTAGE.
+ public static final int V_PRESSURE = 4; // S_BARO. Atmospheric Pressure
+ public static final int V_FORECAST = 5; // S_BARO. Whether forecast. string of "stable", "sunny", "cloudy", "unstable", "thunderstorm" or "unknown"
+ public static final int V_RAIN = 6; // S_RAIN. Amount of rain
+ public static final int V_RAINRATE = 7; // S_RAIN. Rate of rain
+ public static final int V_WIND = 8; // S_WIND. Wind speed
+ public static final int V_GUST = 9; // S_WIND. Gust
+ public static final int V_DIRECTION = 10; // S_WIND. Wind direction 0-360 (degrees)
+ public static final int V_UV = 11; // S_UV. UV light level
+ public static final int V_WEIGHT = 12; // S_WEIGHT. Weight(for scales etc)
+ public static final int V_DISTANCE = 13; // S_DISTANCE. Distance
+ public static final int V_IMPEDANCE = 14; // S_MULTIMETER, S_WEIGHT. Impedance value
+ public static final int V_ARMED = 15; // S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER. Armed status of a security sensor. 1 = Armed, 0 = Bypassed
+ public static final int V_TRIPPED = 16; // S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER, S_WATER_LEAK, S_SOUND, S_VIBRATION, S_MOISTURE. Tripped status of a security sensor. 1 = Tripped, 0
+ public static final int V_WATT = 17; // S_POWER, S_LIGHT, S_DIMMER, S_RGB, S_RGBW. Watt value for power meters
+ public static final int V_KWH = 18; // S_POWER. Accumulated number of KWH for a power meter
+ public static final int V_SCENE_ON = 19; // S_SCENE_CONTROLLER. Turn on a scene
+ public static final int V_SCENE_OFF = 20; // S_SCENE_CONTROLLER. Turn of a scene
+ public static final int V_HEATER = 21; // Deprecated. Use V_HVAC_FLOW_STATE instead.
+ public static final int V_HVAC_FLOW_STATE = 21; // S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver")
+ public static final int V_HVAC_SPEED = 22; // S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto")
+ public static final int V_LIGHT_LEVEL = 23; // S_LIGHT_LEVEL. Uncalibrated light level. 0-100%. Use V_LEVEL for light level in lux
+ public static final int V_VAR1 = 24;
+ public static final int V_VAR2 = 25;
+ public static final int V_VAR3 = 26;
+ public static final int V_VAR4 = 27;
+ public static final int V_VAR5 = 28;
+ public static final int V_UP = 29; // S_COVER. Window covering. Up
+ public static final int V_DOWN = 30; // S_COVER. Window covering. Down
+ public static final int V_STOP = 31; // S_COVER. Window covering. Stop
+ public static final int V_IR_SEND = 32; // S_IR. Send out an IR-command
+ public static final int V_IR_RECEIVE = 33; // S_IR. This message contains a received IR-command
+ public static final int V_FLOW = 34; // S_WATER. Flow of water (in meter)
+ public static final int V_VOLUME = 35; // S_WATER. Water volume
+ public static final int V_LOCK_STATUS = 36; // S_LOCK. Set or get lock status. 1=Locked, 0=Unlocked
+ public static final int V_LEVEL = 37; // S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux)
+ public static final int V_VOLTAGE = 38; // S_MULTIMETER
+ public static final int V_CURRENT = 39; // S_MULTIMETER
+ public static final int V_RGB = 40; // S_RGB_LIGHT, S_COLOR_SENSOR.
+ // Used for sending color information for multi color LED lighting or color sensors.
+ // Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component)
+ public static final int V_RGBW = 41; // S_RGBW_LIGHT
+ // Used for sending color information to multi color LED lighting.
+ // Sent as ASCII hex: RRGGBBWW (WW=white component)
+ public static final int V_ID = 42; // S_TEMP
+ // Used for sending in sensors hardware ids (i.e. OneWire DS1820b).
+ public static final int V_UNIT_PREFIX = 43; // S_DUST, S_AIR_QUALITY
+ // Allows sensors to send in a string representing the
+ // unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch.
+ // Can be used for S_DISTANCE or gas concentration
+ public static final int V_HVAC_SETPOINT_COOL = 44; // S_HVAC. HVAC cool setpoint (Integer between 0-100)
+ public static final int V_HVAC_SETPOINT_HEAT = 45; // S_HEATER, S_HVAC. HVAC/Heater setpoint (Integer between 0-100)
+ public static final int V_HVAC_FLOW_MODE = 46; // S_HVAC. Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn")
+
+ // Type of internal messages (for internal messages)
+ public static final int I_BATTERY_LEVEL = 0;
+ public static final int I_TIME = 1;
+ public static final int I_VERSION = 2;
+ public static final int I_ID_REQUEST = 3;
+ public static final int I_ID_RESPONSE = 4;
+ public static final int I_INCLUSION_MODE = 5;
+ public static final int I_CONFIG = 6;
+ public static final int I_FIND_PARENT = 7;
+ public static final int I_FIND_PARENT_RESPONSE = 8;
+ public static final int I_LOG_MESSAGE = 9;
+ public static final int I_CHILDREN = 10;
+ public static final int I_SKETCH_NAME = 11;
+ public static final int I_SKETCH_VERSION = 12;
+ public static final int I_REBOOT = 13;
+ public static final int I_GATEWAY_READY = 14;
+ public static final int I_REQUEST_SIGNING = 15;
+ public static final int I_GET_NONCE = 16;
+ public static final int I_GET_NONCE_RESPONSE = 17;
+
+ // Type of data stream (for streamed message)
+ public static final int ST_FIRMWARE_CONFIG_REQUEST = 0;
+ public static final int ST_FIRMWARE_CONFIG_RESPONSE = 1;
+ public static final int ST_FIRMWARE_REQUEST = 2;
+ public static final int ST_FIRMWARE_RESPONSE = 3;
+ public static final int ST_SOUND = 4;
+ public static final int ST_IMAGE = 5;
+
+ // Message attributes
+ /// Peer-node-id(Dest);Remote-node-id(Orig);Cmd;Ack;Type;Payload\n
+ public boolean m_msgOK = false;
+ public int m_dest;
+ public int m_orig;
+ public int m_cmd;
+ public int m_ack;
+ public int m_type;
+ public String m_payload;
+
+ public boolean parseString(String line) {
+ m_msgOK = false;
+ String strMsg;
+ char lastCh = line.charAt(line.length()-1);
+ if( lastCh == '\r' || lastCh == '\n') {
+ strMsg = line.substring(line.length() - 1);
+ } else {
+ strMsg = line;
+ }
+ String[] attributes = strMsg.split(";");
+ if( attributes.length >= 5 ) {
+ m_dest = Integer.parseInt(attributes[0]);
+ m_orig = Integer.parseInt(attributes[1]);
+ m_cmd = Integer.parseInt(attributes[2]);
+ m_ack = Integer.parseInt(attributes[3]);
+ m_type = Integer.parseInt(attributes[4]);
+ m_payload = "";
+ for( int i = 5; i < attributes.length; i++ ) {
+ if( i > 5 ) m_payload += ";";
+ m_payload += attributes[i];
+ }
+ m_msgOK = true;
+ }
+ return m_msgOK;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/xltDataAccess.java b/app/src/main/java/ca/xlight/demoapp/SDK/xltDataAccess.java
new file mode 100644
index 0000000..90e771c
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/xltDataAccess.java
@@ -0,0 +1,10 @@
+package ca.xlight.demoapp.SDK;
+
+/**
+ * Created by sunboss on 2016-11-16.
+ */
+
+// Data Manipulate Interface (DMI)
+public class xltDataAccess {
+ private static final String TAG = xltDataAccess.class.getSimpleName();
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/SDK/xltDevice.java b/app/src/main/java/ca/xlight/demoapp/SDK/xltDevice.java
new file mode 100644
index 0000000..42ae128
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/SDK/xltDevice.java
@@ -0,0 +1,1497 @@
+package ca.xlight.demoapp.SDK;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+
+import ca.xlight.demoapp.SDK.BLE.BLEPairedDeviceList;
+import ca.xlight.demoapp.SDK.Cloud.CloudBridge;
+import ca.xlight.demoapp.SDK.Cloud.ParticleAdapter;
+import ca.xlight.demoapp.SDK.LAN.LANBridge;
+import ca.xlight.demoapp.SDK.BLE.BLEBridge;
+
+import java.util.ArrayList;
+
+/**
+ * Created by sunboss on 2016-11-15.
+ *
+ * Version: 0.1
+ *
+ * Please report bug at bs.sun@datatellit.com,
+ * or send pull request to sunbaoshi1975/Android-XlightSmartController
+ *
+ */
+
+@SuppressWarnings({"UnusedDeclaration"})
+// Smart Device
+public class xltDevice {
+
+ //-------------------------------------------------------------------------
+ // misc
+ //-------------------------------------------------------------------------
+ private static final String TAG = xltDevice.class.getSimpleName();
+ public static final int DEFAULT_DEVICE_ID = 1;
+ public static final String DEFAULT_DEVICE_NAME = "";
+ public static final String DEFAULT_DEVICE_BLENAME = BLEPairedDeviceList.XLIGHT_BLE_NAME_PREFIX + DEFAULT_DEVICE_NAME;
+
+
+ // on/off values
+ public static final int STATE_OFF = 0;
+ public static final int STATE_ON = 1;
+ public static final int STATE_TOGGLE = 2;
+
+ // default alarm/filter id
+ public static final int DEFAULT_ALARM_ID = 255;
+ public static final int DEFAULT_FILTER_ID = 0;
+
+ // Event Names
+ public static final String eventAlarm = "xlc-event-alarm";
+ public static final String eventDeviceConfig = "xlc-config-device";
+ public static final String eventDeviceStatus = "xlc-status-device";
+ public static final String eventSensorData = "xlc-data-sensor";
+
+ // Broadcast Intent
+ public static final String bciAlarm = "io.xlight.SDK." + eventAlarm;
+ public static final String bciDeviceConfig = "io.xlight.SDK." + eventDeviceConfig;
+ public static final String bciDeviceStatus = "io.xlight.SDK." + eventDeviceStatus;
+ public static final String bciSensorData = "io.xlight.SDK." + eventSensorData;
+
+ // Timeout constants
+ private static final int TIMEOUT_CLOUD_LOGIN = 15;
+
+ //-------------------------------------------------------------------------
+ // Constants
+ //-------------------------------------------------------------------------
+ public static final int MAX_RING_NUM = 3;
+ public static final int RING_ID_ALL = 0;
+ public static final int RING_ID_1 = 1;
+ public static final int RING_ID_2 = 2;
+ public static final int RING_ID_3 = 3;
+
+ public static final int BR_MIN_VALUE = 1;
+ public static final int CT_MIN_VALUE = 2700;
+ public static final int CT_MAX_VALUE = 6500;
+ public static final int CT_SCOPE = 38;
+ public static final int CT_STEP = ((CT_MAX_VALUE-CT_MIN_VALUE)/10);
+
+ // Command values for JSONCommand Interface
+ public static final int CMD_SERIAL = 0;
+ public static final int CMD_POWER = 1;
+ public static final int CMD_COLOR = 2;
+ public static final int CMD_BRIGHTNESS = 3;
+ public static final int CMD_SCENARIO = 4;
+ public static final int CMD_CCT = 5;
+ public static final int CMD_QUERY = 6;
+ public static final int CMD_EFFECT = 7;
+ public static final int CMD_EXT = 8;
+
+ // NodeID Convention
+ public static final int NODEID_GATEWAY = 0;
+ public static final int NODEID_MAINDEVICE = 1;
+ public static final int NODEID_MIN_DEVCIE = 8;
+ public static final int NODEID_MAX_DEVCIE = 63;
+ public static final int NODEID_MIN_REMOTE = 64;
+ public static final int NODEID_MAX_REMOTE = 127;
+ public static final int NODEID_PROJECTOR = 128;
+ public static final int NODEID_SMARTPHONE = 139;
+ public static final int NODEID_DUMMY = 255;
+
+ // Device (lamp) type
+ public static final int devtypUnknown = 0;
+ public static final int devtypCRing3 = 1;
+ public static final int devtypCRing2 = 2;
+ public static final int devtypCBar = 3;
+ public static final int devtypCFrame = 4;
+ public static final int devtypCWave = 5;
+ public static final int devtypCRing1 = 31;
+
+ public static final int devtypWRing3 = 32;
+ public static final int devtypWRing2 = 33;
+ public static final int devtypWBar = 34;
+ public static final int devtypWFrame = 35;
+ public static final int devtypWWave = 36;
+ public static final int devtypWSquare60 = 37;
+ public static final int devtypWPanel120_30 = 38;
+ public static final int devtypWBlackboard = 39;
+ public static final int devtypWRing1 = 95;
+
+ public static final int devtypMRing3 = 96;
+ public static final int devtypMRing2 = 97;
+ public static final int devtypMBar = 98;
+ public static final int devtypMFrame = 99;
+ public static final int devtypMWave = 100;
+ public static final int devtypMRing1 = 127;
+
+ public static final int devtypSwitch = 129;
+ public static final int devtypSwitch2 = 130;
+ public static final int devtypSwitch3 = 131;
+ public static final int devtypSwitch4 = 132;
+ public static final int devtypDummy = 255;
+
+ public static final int DEFAULT_DEVICE_TYPE = devtypWRing3;
+
+ // Filter (special effect)
+ public static final int FILTER_SP_EF_NONE = 0; // Disable special effect
+ public static final int FILTER_SP_EF_BREATH = 1; // Normal breathing light
+ public static final int FILTER_SP_EF_FAST_BREATH = 2; // Fast breathing light
+ public static final int FILTER_SP_EF_FLORID = 3; // Randomly altering color
+ public static final int FILTER_SP_EF_FAST_FLORID = 4; // Fast randomly altering color
+
+ // Bridge Connection Status
+ public static final int BCS_FUNCTION_COREID = 8;
+ public static final int BCS_FUNCTION_ACK = 9;
+ public static final int BCS_NOT_CONNECTED = 10;
+ public static final int BCS_CONNECTING = 11;
+ public static final int BCS_CONNECTED = 12;
+ public static final int BCS_CONNECTION_FAILED = 13;
+ public static final int BCS_CONNECTION_LOST = 14;
+
+ // Wi-Fi Authenticate Method
+ public static final int WLAN_SEC_UNSEC = 0;
+ public static final int WLAN_SEC_WEP = 1;
+ public static final int WLAN_SEC_WPA = 2;
+ public static final int WLAN_SEC_WPA2 = 3;
+
+ // Wi-Fi Cipher Method
+ public static final int WLAN_CIPHER_NOT_SET = 0;
+ public static final int WLAN_CIPHER_AES = 1;
+ public static final int WLAN_CIPHER_TKIP = 2;
+ public static final int WLAN_CIPHER_AES_TKIP = 3;
+
+ public enum BridgeType {
+ NONE,
+ Cloud,
+ BLE,
+ LAN
+ }
+
+ //-------------------------------------------------------------------------
+ // Callback Interfaces
+ //-------------------------------------------------------------------------
+ // OnConnected
+ public interface callbackConnect {
+ void onConnected(final BridgeType bridge, final boolean connected);
+ }
+
+ //-------------------------------------------------------------------------
+ // Ring of Smart Fixture
+ //-------------------------------------------------------------------------
+ public class xltRing {
+ // Lights Status
+ public int m_State = 0;
+ public int m_Brightness = 50;
+ public int m_CCT = CT_MIN_VALUE;
+ public int m_R = 0;
+ public int m_G = 0;
+ public int m_B = 0;
+ public int m_String1 = 50;
+ public int m_String2 = 50;
+ public int m_String3 = 50;
+
+ public boolean isSameColor(final xltRing that) {
+ if( this.m_State != that.m_State ) return false;
+ if( this.m_CCT != that.m_CCT ) return false;
+ if( this.m_R != that.m_R ) return false;
+ if( this.m_G != that.m_G ) return false;
+ if( this.m_B != that.m_B ) return false;
+ return true;
+ }
+
+ public boolean isSameBright(final xltRing that) {
+ if( this.m_State != that.m_State ) return false;
+ if( this.m_CCT != that.m_CCT ) return false;
+ if( this.m_Brightness != that.m_Brightness ) return false;
+ return true;
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Sensor Data
+ //-------------------------------------------------------------------------
+ public class SensorData {
+ public float m_RoomTemp = 24; // Room temperature
+ public int m_RoomHumidity = 40; // Room humidity
+ public int m_RoomBrightness = 0; // ALS value
+ public int m_Mic = 0; // MIC value
+ public int m_PIR = 0; // PIR value
+ public int m_GAS = 0; // Gas value
+ public int m_Smoke = 0; // Smoke value
+ public int m_PM25 = 0; // PM2.5 value
+ public int m_Noise = 0; // Noise value
+
+ public float m_OutsideTemp = 23; // Local outside temperature
+ public int m_OutsideHumidity = 30; // Local outside humidity
+ }
+
+ //-------------------------------------------------------------------------
+ // Device / Node under the Controller
+ //-------------------------------------------------------------------------
+ public class xltNodeInfo {
+ public int m_ID = 0;
+ public int m_Type;
+ public boolean m_isUp;
+ public int m_filter;
+ public String m_Name;
+ public int m_kms; // Relay Keys Status (bitmap)
+ // Rings
+ public xltRing[] m_Ring = new xltRing[MAX_RING_NUM];
+ }
+
+ //-------------------------------------------------------------------------
+ // Variables
+ //-------------------------------------------------------------------------
+ // Profile
+ private static boolean m_bInitialized = false;
+ private String m_ControllerID;
+ private int m_DevID = DEFAULT_DEVICE_ID;
+
+ // Bridge Objects
+ private CloudBridge cldBridge;
+ private BLEBridge bleBridge;
+ private LANBridge lanBridge;
+
+ // Bridge Selection
+ private BridgeType m_currentBridge = BridgeType.NONE;
+ private boolean m_autoBridge = true;
+
+ // Device/Node List
+ private ArrayList m_lstNodes = new ArrayList<>();
+ xltNodeInfo m_currentNode = null;
+
+ // Sensor Data
+ public SensorData m_Data;
+
+ // Event Notification
+ private boolean m_enableEventBroadcast = false;
+ private boolean m_enableEventSendMessage = true;
+
+ // Event Handler List
+ private Handler m_bcsHandler = null;
+ private ArrayList m_lstEH_DevST = new ArrayList<>();
+ private ArrayList m_lstEH_SenDT = new ArrayList<>();
+
+ // Callback members
+ public callbackConnect m_onConnected = null;
+
+ //-------------------------------------------------------------------------
+ // Regular Interfaces
+ //-------------------------------------------------------------------------
+ public xltDevice() {
+ super();
+
+ // Create member objects
+ m_Data= new SensorData();
+ cldBridge = new CloudBridge();
+ bleBridge = new BLEBridge();
+ lanBridge = new LANBridge();
+ }
+
+ // Initialize objects
+ public void Init(Context context) {
+ // Clear event handler lists
+ clearDeviceEventHandlerList();
+ clearDataEventHandlerList();
+ clearDeviceList();
+
+ // Ensure we do it only once
+ if( !m_bInitialized ) {
+ // Init BLE Adapter
+ if( !BLEPairedDeviceList.initialized() ) {
+ BLEPairedDeviceList.init(context);
+ }
+
+ // Init Particle Adapter
+ if( !ParticleAdapter.initialized() ) {
+ ParticleAdapter.init(context);
+ // ToDo: get login credential or access token from DMI
+ // make sure we logged onto IoT cloud
+ ParticleAdapter.authenticate();
+ }
+
+ m_bInitialized = true;
+ }
+
+ // Update parent context
+ setParentContext(context);
+
+ // Set me as the parent device
+ setParentDevice();
+
+ // Set priority for each bridge - the bigger, the higher
+ cldBridge.setPriority(6);
+ bleBridge.setPriority(9);
+ lanBridge.setPriority(3);
+ }
+
+ // Connect to message bridges
+ public boolean Connect(final String controllerID) {
+ return Connect(controllerID, null, null);
+ }
+
+ // Connect to message bridges with bridge connection status handler
+ public boolean Connect(final String controllerID, final Handler handler) {
+ return Connect(controllerID, handler, null);
+ }
+
+ // Connect to message bridges with callback
+ public boolean Connect(final String controllerID, callbackConnect onConnected) {
+ return Connect(controllerID, null, onConnected);
+ }
+
+ // Connect to message bridges with bridge connection status handler & callback
+ public boolean Connect(final String controllerID, final Handler handler, callbackConnect onConnected) {
+ // Set callback
+ this.m_bcsHandler = handler;
+ this.m_onConnected = onConnected;
+
+ // ToDo: get device (node) list: devID, devType, devName & devBLEName by controllerID from DMI
+ // If DMI cannot communicate to the Cloud, return the most recent values in cookie,
+ // If there is no cookie, return default values.
+ m_ControllerID = controllerID;
+ // ToDo: Add device/node list
+ if( m_lstNodes.size() <= 0 ) {
+ // Easy for testing
+ addNodeToDeviceList(DEFAULT_DEVICE_ID, DEFAULT_DEVICE_TYPE, DEFAULT_DEVICE_NAME);
+ }
+ //...
+ //bleBridge.setName(devBLEName);
+ bleBridge.setName(DEFAULT_DEVICE_BLENAME);
+
+ // Connect to Cloud
+ ConnectCloud();
+
+ // Connect to BLE
+ ConnectBLE();
+
+ // Connect to LAN
+ ConnectLAN();
+
+ return true;
+ }
+
+ // Disconnect all bridges from controller
+ public boolean Disconnect() {
+ DisconnectCloud();
+ DisconnectBLE();
+ DisconnectLAN();
+ return true;
+ }
+
+ public boolean ConnectCloud() {
+ if( !m_bInitialized ) return false;
+ if( m_ControllerID.length() == 0 ) return false;
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Check ControllerID
+ int timeout = TIMEOUT_CLOUD_LOGIN;
+ while( !ParticleAdapter.isAuthenticated() && timeout-- > 0 ) {
+ SystemClock.sleep(1500);
+ }
+ if( ParticleAdapter.isAuthenticated() ) {
+ if (ParticleAdapter.checkDeviceID(m_ControllerID)) {
+ // Connect Cloud Instance
+ cldBridge.connectCloud(m_ControllerID);
+ }
+ } else {
+ onBridgeStatusChanged(BridgeType.Cloud, BCS_CONNECTION_FAILED);
+ }
+ }
+ }).start();
+ return true;
+ }
+
+ public boolean DisconnectCloud() {
+ if( isCloudOK() ) {
+ cldBridge.disconnectCloud();
+ }
+ return !isCloudOK();
+ }
+
+ public boolean ConnectBLE() {
+ return(bleBridge.connectController());
+ }
+
+ public boolean DisconnectBLE() {
+ if( isBLEOK() ) {
+ bleBridge.disconnectController();
+ }
+ return !isBLEOK();
+ }
+
+ public boolean ConnectLAN() {
+ // ToDo: get IP & Port from Cloud or BLE (SmartController told it)
+ return(lanBridge.connectController("192.168.0.114", 5555));
+ }
+
+ public boolean DisconnectLAN() {
+ if( isLANOK() ) {
+ lanBridge.disconnectController();
+ }
+ return !isLANOK();
+ }
+
+ public boolean isSunny() {
+ return(m_currentNode != null ? isSunny(m_currentNode.m_Type) : false);
+ }
+
+ public boolean isRainbow() {
+ return(m_currentNode != null ? isRainbow(m_currentNode.m_Type) : false);
+ }
+
+ public boolean isMirage() {
+ return(m_currentNode != null ? isMirage(m_currentNode.m_Type) : false);
+ }
+
+ public boolean isSwitch() {
+ return(m_currentNode != null ? isSwitch(m_currentNode.m_Type) : false);
+ }
+
+ public boolean isSunny(final int DevType) {
+ return(DevType >= devtypWRing3 && DevType <= devtypWRing1);
+ }
+
+ public boolean isRainbow(final int DevType) {
+ return(DevType >= devtypCRing3 && DevType <= devtypCRing1);
+ }
+
+ public boolean isMirage(final int DevType) {
+ return(DevType >= devtypMRing3 && DevType <= devtypMRing1);
+ }
+
+ public boolean isSwitch(final int DevType) {
+ return(DevType >= devtypSwitch && DevType <= devtypSwitch4);
+ }
+
+ private int getRingIndex(final int ringID) {
+ return((ringID >= RING_ID_1 && ringID <= RING_ID_3) ? ringID - 1 : 0);
+ }
+
+ //-------------------------------------------------------------------------
+ // Property Access Interfaces
+ //-------------------------------------------------------------------------
+ public String getControllerID() {
+ return m_ControllerID;
+ }
+
+ public int addNodeToDeviceList(final int devID, final int devType, final String devName) {
+ xltNodeInfo lv_node = new xltNodeInfo();
+ lv_node.m_ID = devID;
+ lv_node.m_Type = devType;
+ lv_node.m_isUp = false;
+ lv_node.m_filter = FILTER_SP_EF_NONE;
+ lv_node.m_Name = devName;
+ for(int i = 0; i < MAX_RING_NUM; i++) {
+ lv_node.m_Ring[i] = new xltRing();
+ }
+ m_lstNodes.add(lv_node);
+ if( m_currentNode == null ) {
+ m_currentNode = lv_node;
+ m_DevID = devID;
+ }
+ return m_lstNodes.size();
+ }
+
+ public int findNodeFromDeviceList(final int devID) {
+ for (xltNodeInfo lv_node : m_lstNodes) {
+ if( lv_node.m_ID == devID ) {
+ return m_lstNodes.indexOf(lv_node);
+ }
+ }
+ return -1;
+ }
+
+ public boolean removeNodeFromDeviceList(final int devID) {
+ int lv_index = findNodeFromDeviceList(devID);
+ if( lv_index >= 0 ) {
+ if( m_currentNode != null ) {
+ if( m_currentNode.m_ID == devID ) m_currentNode = null;
+ }
+ m_lstNodes.remove(lv_index);
+ return true;
+ }
+ return false;
+ }
+
+ public void clearDeviceList() {
+ m_currentNode = null;
+ m_lstNodes.clear();
+ }
+
+ public int getDeviceID() {
+ return m_DevID;
+ }
+
+ // Change current device / node
+ public void setDeviceID(final int devID) {
+ m_DevID = devID;
+ int lv_index = findNodeFromDeviceList(devID);
+ if( lv_index >= 0 ) {
+ m_currentNode = m_lstNodes.get(lv_index);
+ }
+ }
+
+ private void setParentContext(Context context) {
+ cldBridge.setParentContext(context);
+ bleBridge.setParentContext(context);
+ lanBridge.setParentContext(context);
+ }
+
+ private void setParentDevice() {
+ cldBridge.setParentDevice(this);
+ bleBridge.setParentDevice(this);
+ lanBridge.setParentDevice(this);
+ }
+
+ public int getDeviceType() {
+ return(m_currentNode != null ? m_currentNode.m_Type : devtypDummy);
+ }
+
+ public int getDeviceType(final int nodeID) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_Type;
+ }
+ return(devtypUnknown);
+ }
+
+ public void setDeviceType(final int type) {
+ setDeviceType(m_DevID, type);
+ }
+
+ public void setDeviceType(final int nodeID, final int type) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_Type = type;
+ }
+ }
+
+ public static boolean getBit(final int value, final int position)
+ {
+ return ((value & (1 << position)) != 0);
+ }
+
+ public static int setBit(final int value, final int position, final boolean on_off)
+ {
+ if( on_off ) {
+ return (value | (1 << position));
+
+ } else {
+ int mask = ~(1 << position);
+ return (value & (mask));
+ }
+ }
+
+ public boolean getKMSBit(final int key) {
+ return(m_currentNode != null ? getBit(m_currentNode.m_kms, key) : false);
+ }
+
+ public boolean getKMSBit(final int nodeID, final int key) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return getBit(m_lstNodes.get(lv_dev).m_kms, key);
+ }
+ return(false);
+ }
+
+ public void setKMSBit(final int key, final boolean on_off) {
+ setKMSBit(m_DevID, key, on_off);
+ }
+
+ public void setKMSBit(final int nodeID, final int key, final boolean on_off) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_kms = setBit(m_lstNodes.get(lv_dev).m_kms, key, on_off);
+ }
+ }
+
+ public int getKMState() {
+ return(m_currentNode != null ? m_currentNode.m_kms : 0);
+ }
+
+ public int getKMState(final int nodeID) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_kms;
+ }
+ return(devtypUnknown);
+ }
+
+ public void setKMState(final int kms) {
+ setKMState(m_DevID, kms);
+ }
+
+ public void setKMState(final int nodeID, final int kms) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_kms = kms;
+ }
+ }
+
+ public void updateKMState(final String Positions, final boolean on_off) {
+ updateKMState(m_DevID, Positions, on_off);
+ }
+
+ public void updateKMState(final int nodeID, final String Positions, final boolean on_off) {
+ for (int i = 0; i < Positions.length(); i++) {
+ char ch = Positions.charAt(i);
+ if( ch > '0' && ch < '9' ) {
+ int pos = ch - 49;
+ setKMSBit(nodeID, pos, on_off);
+ }
+ }
+ }
+
+ public String getDeviceName() {
+ return(m_currentNode != null ? m_currentNode.m_Name : "");
+ }
+
+ public String getDeviceName(final int nodeID) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_Name;
+ }
+ return("");
+ }
+
+ public void setDeviceName(final String name) {
+ setDeviceName(m_DevID, name);
+ }
+
+ public void setDeviceName(final int nodeID, final String name) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_Name = name;
+ }
+ }
+
+ public boolean getNodeAlive() {
+ return getNodeAlive(m_DevID);
+ }
+
+ public boolean getNodeAlive(final int nodeID) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_isUp;
+ }
+ return(false);
+ }
+
+ public void setNodeAlive(final boolean isUp) {
+ setNodeAlive(m_DevID, isUp);
+ }
+
+ public void setNodeAlive(final int nodeID, final boolean isUp) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_isUp = isUp;
+ }
+ }
+
+ public int getFilter() {
+ return getFilter(m_DevID);
+ }
+
+ public int getFilter(final int nodeID) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_filter;
+ }
+ return(FILTER_SP_EF_NONE);
+ }
+
+ public void setFilter(final int filter) {
+ setFilter(m_DevID, filter);
+ }
+
+ public void setFilter(final int nodeID, final int filter) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ m_lstNodes.get(lv_dev).m_filter = filter;
+ }
+ }
+
+ public int getState() {
+ return(getState(m_DevID));
+ }
+
+ public int getState(final int nodeID) {
+ return(getState(nodeID, RING_ID_ALL));
+ }
+
+ public int getState(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_Ring[index].m_State;
+ }
+ return(-1);
+ }
+
+ public void setState(final int state) {
+ setState(m_DevID, state);
+ }
+
+ public void setState(final int nodeID, final int state) {
+ setState(nodeID, RING_ID_ALL, state);
+ }
+
+ public void setState(final int nodeID, final int ringID, final int state) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_State = state;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_State = state;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_State = state;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_State = state;
+ }
+ }
+ }
+
+ public int getBrightness() {
+ return(getBrightness(m_DevID));
+ }
+
+ public int getBrightness(final int nodeID) {
+ return(getBrightness(nodeID, RING_ID_ALL));
+ }
+
+ public int getBrightness(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_Ring[index].m_Brightness;
+ }
+ return(-1);
+ }
+
+ public void setBrightness(final int brightness) {
+ setBrightness(m_DevID, brightness);
+ }
+
+ public void setBrightness(final int nodeID, final int brightness) {
+ setBrightness(nodeID, RING_ID_ALL, brightness);
+ }
+
+ public void setBrightness(final int nodeID, final int ringID, final int brightness) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_Brightness = brightness;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_Brightness = brightness;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_Brightness = brightness;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_Brightness = brightness;
+ }
+ }
+ }
+
+ public int getCCT() {
+ return(getCCT(m_DevID));
+ }
+
+ public int getCCT(final int nodeID) {
+ return(getCCT(nodeID, RING_ID_ALL));
+ }
+
+ public int getCCT(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return m_lstNodes.get(lv_dev).m_Ring[index].m_CCT;
+ }
+ return(-1);
+ }
+
+ public void setCCT(final int nodeID, final int cct) {
+ setCCT(nodeID, RING_ID_ALL, cct);
+ }
+
+ public void setCCT(final int cct) {
+ setCCT(m_DevID, cct);
+ }
+
+ public void setCCT(final int nodeID, final int ringID, final int cct) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_CCT = cct;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_CCT = cct;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_CCT = cct;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_CCT = cct;
+ }
+ }
+ }
+
+ public int getWhite() {
+ return(getWhite(m_DevID));
+ }
+
+ public int getWhite(final int nodeID) {
+ return(getWhite(nodeID, RING_ID_ALL));
+ }
+
+ public int getWhite(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return(m_lstNodes.get(lv_dev).m_Ring[index].m_CCT % 256);
+ }
+ return(-1);
+ }
+
+ public void setWhite(final int white) {
+ setWhite(m_DevID, white);
+ }
+
+ public void setWhite(final int nodeID, final int white) {
+ setWhite(nodeID, RING_ID_ALL, white);
+ }
+
+ public void setWhite(final int nodeID, final int ringID, final int white) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_CCT = white;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_CCT = white;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_CCT = white;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_CCT = white;
+ }
+ }
+ }
+
+ public int getRed() {
+ return(getRed(m_DevID));
+ }
+
+ public int getRed(final int nodeID) {
+ return(getRed(nodeID, RING_ID_ALL));
+ }
+
+ public int getRed(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return(m_lstNodes.get(lv_dev).m_Ring[index].m_R);
+ }
+ return(-1);
+ }
+
+ public void setRed(final int red) {
+ setRed(m_DevID, red);
+ }
+
+ public void setRed(final int nodeID, final int red) {
+ setRed(nodeID, RING_ID_ALL, red);
+ }
+
+ public void setRed(final int nodeID, final int ringID, final int red) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_R = red;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_R = red;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_R = red;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_R = red;
+ }
+ }
+ }
+
+ public int getGreen() {
+ return(getGreen(m_DevID));
+ }
+
+ public int getGreen(final int nodeID) {
+ return(getGreen(nodeID, RING_ID_ALL));
+ }
+
+ public int getGreen(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return(m_lstNodes.get(lv_dev).m_Ring[index].m_G);
+ }
+ return(-1);
+ }
+
+ public void setGreen(final int green) {
+ setGreen(m_DevID, green);
+ }
+
+ public void setGreen(final int nodeID, final int green) {
+ setGreen(nodeID, RING_ID_ALL, green);
+ }
+
+ public void setGreen(final int nodeID, final int ringID, final int green) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_G = green;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_G = green;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_G = green;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_G = green;
+ }
+ }
+ }
+
+ public int getBlue() {
+ return(getBlue(m_DevID));
+ }
+
+ public int getBlue(final int nodeID) {
+ return(getBlue(nodeID, RING_ID_ALL));
+ }
+
+ public int getBlue(final int nodeID, final int ringID) {
+ int index = getRingIndex(ringID);
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ return(m_lstNodes.get(lv_dev).m_Ring[index].m_B);
+ }
+ return(-1);
+ }
+
+ public void setBlue(final int blue) {
+ setBlue(m_DevID, blue);
+ }
+
+ public void setBlue(final int nodeID, final int blue) {
+ setBlue(nodeID, RING_ID_ALL, blue);
+ }
+
+ public void setBlue(final int nodeID, final int ringID, final int blue) {
+ int lv_dev = findNodeFromDeviceList(nodeID);
+ if( lv_dev >= 0 ) {
+ if (ringID == RING_ID_ALL) {
+ m_lstNodes.get(lv_dev).m_Ring[0].m_B = blue;
+ m_lstNodes.get(lv_dev).m_Ring[1].m_B = blue;
+ m_lstNodes.get(lv_dev).m_Ring[2].m_B = blue;
+ } else {
+ int index = getRingIndex(ringID);
+ m_lstNodes.get(lv_dev).m_Ring[index].m_B = blue;
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Bridge Selection Interfaces
+ //-------------------------------------------------------------------------
+ public String getBridgeInfo() {
+ return getBridgeInfo(m_currentBridge);
+ }
+
+ public String getBridgeInfo(final BridgeType bridge) {
+ String desc;
+ switch(bridge) {
+ case Cloud:
+ desc = "Cloud bridge " + (isCloudOK() ? "connected" : "not connected");
+ break;
+ case BLE:
+ desc = bleBridge.getName() + " " + (isBLEOK() ? "connected" : "not connected");
+ //desc += " Peer: ";
+ break;
+ case LAN:
+ desc = lanBridge.getName() + " " + (isLANOK() ? "connected" : "not connected");
+ //desc += " IP: " + " Port: ";
+ break;
+ default:
+ desc = "No bridge available";
+ }
+ return desc;
+ }
+
+ public boolean isCloudOK() {
+ return(cldBridge.isConnected());
+ }
+
+ public boolean isBLEOK() {
+ return(bleBridge.isConnected());
+ }
+
+ public boolean isLANOK() {
+ return(lanBridge.isConnected());
+ }
+
+ public boolean isBridgeOK(final BridgeType bridge) {
+ switch(bridge) {
+ case Cloud:
+ return isCloudOK();
+ case BLE:
+ return isBLEOK();
+ case LAN:
+ return isLANOK();
+ }
+ return false;
+ }
+
+ public boolean getAutoBridge() {
+ return m_autoBridge;
+ }
+
+ public void setAutoBridge(final boolean auto) {
+ m_autoBridge = auto;
+ }
+
+ public void setBridgePriority(final BridgeType bridge, final int priority) {
+ if( bridge == BridgeType.Cloud ) {
+ cldBridge.setPriority(priority);
+ } else if( bridge == BridgeType.BLE ) {
+ bleBridge.setPriority(priority);
+ } else if( bridge == BridgeType.LAN ) {
+ lanBridge.setPriority(priority);
+ }
+ }
+
+ private BridgeType selectBridge() {
+ /// Use current bridge as long as available
+ if( isBridgeOK(m_currentBridge) ) return m_currentBridge;
+
+ if( getAutoBridge() ) {
+ int maxPri = 0;
+ if (isCloudOK() && cldBridge.getPriority() > maxPri) {
+ m_currentBridge = BridgeType.Cloud;
+ maxPri = cldBridge.getPriority();
+ }
+
+ if (isBLEOK() && bleBridge.getPriority() > maxPri) {
+ m_currentBridge = BridgeType.BLE;
+ maxPri = bleBridge.getPriority();
+ }
+
+ if (isLANOK() && lanBridge.getPriority() > maxPri) {
+ m_currentBridge = BridgeType.LAN;
+ maxPri = lanBridge.getPriority();
+ }
+
+ if (maxPri == 0) {
+ m_currentBridge = BridgeType.NONE;
+ }
+ }
+
+ return m_currentBridge;
+ }
+
+ // Manually set bridge
+ public boolean useBridge(final BridgeType bridge) {
+ if( bridge == BridgeType.Cloud && !isCloudOK() ) {
+ return false;
+ }
+ if( bridge == BridgeType.BLE && !isBLEOK() ) {
+ return false;
+ }
+ if( bridge == BridgeType.LAN && !isLANOK() ) {
+ return false;
+ }
+
+ m_currentBridge = bridge;
+ return true;
+ }
+
+ public BridgeType getCurrentBridge() {
+ return m_currentBridge;
+ }
+
+ //-------------------------------------------------------------------------
+ // Device Control Interfaces (DCI)
+ //-------------------------------------------------------------------------
+ // Query Status
+ public int QueryStatus() {
+ return QueryStatus(m_DevID);
+ }
+
+ public int QueryStatus(final int nodeID) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandQueryDevice(nodeID);
+ break;
+ case BLE:
+ rc = bleBridge.QueryStatus(nodeID);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Turn On / Off
+ public int PowerSwitch(final int state) {
+ return PowerSwitch(m_DevID, state);
+ }
+
+ public int PowerSwitch(final int nodeID, final int state) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.FastCallPowerSwitch(nodeID, state);
+ break;
+ case BLE:
+ rc = bleBridge.PowerSwitch(nodeID, state);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Change Brightness
+ public int ChangeBrightness(final int value) {
+ return ChangeBrightness(m_DevID, value);
+ }
+
+ public int ChangeBrightness(final int nodeID, final int value) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandBrightness(nodeID, value);
+ break;
+ case BLE:
+ rc = bleBridge.ChangeBrightness(nodeID, value);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Change CCT
+ public int ChangeCCT(final int value) {
+ return ChangeCCT(m_DevID, value);
+ }
+
+ public int ChangeCCT(final int nodeID, final int value) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandCCT(nodeID, value);
+ break;
+ case BLE:
+ rc = bleBridge.ChangeCCT(nodeID, value);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Change Color (RGBW)
+ public int ChangeColor(final int ring, final boolean state, final int br, final int ww, final int r, final int g, final int b) {
+ return ChangeColor(m_DevID, ring, state, br, ww, r, g, b);
+ }
+
+ public int ChangeColor(final int nodeID, final int ring, final boolean state, final int br, final int ww, final int r, final int g, final int b) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandColor(nodeID, ring, state, br, ww, r, g, b);
+ break;
+ case BLE:
+ rc = bleBridge.ChangeColor(nodeID, ring, state, br, ww, r, g, b);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Change Scenario
+ public int ChangeScenario(final int scenario) {
+ return ChangeScenario(m_DevID, scenario);
+ }
+
+ public int ChangeScenario(final int nodeID, final int scenario) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandScenario(nodeID, scenario);
+ break;
+ case BLE:
+ rc = bleBridge.ChangeScenario(nodeID, scenario);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Set Special Effect
+ public int SetSpecialEffect(final int filter) {
+ return SetSpecialEffect(m_DevID, filter);
+ }
+
+ public int SetSpecialEffect(final int nodeID, final int filter) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandSpecialEffect(nodeID, filter);
+ break;
+ case BLE:
+ rc = bleBridge.SetSpecialEffect(nodeID, filter);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ // Switch On / Off
+ public int KMSwitch(final boolean on_off, final String keys) {
+ return KMSwitch(m_DevID, on_off, keys);
+ }
+
+ public int KMSwitch(final int nodeID, final boolean on_off, final String keys) {
+ int rc = -1;
+
+ // Select Bridge
+ selectBridge();
+ if( isBridgeOK(m_currentBridge) ) {
+ switch(m_currentBridge) {
+ case Cloud:
+ rc = cldBridge.JSONCommandRelaySwitch(nodeID, on_off, keys);
+ break;
+ case BLE:
+ rc = bleBridge.SetRelayKey(nodeID, on_off, keys);
+ break;
+ case LAN:
+ // ToDo: call LAN API
+ break;
+ }
+ }
+ return rc;
+ }
+
+ //-------------------------------------------------------------------------
+ // System Config Interfaces
+ //-------------------------------------------------------------------------
+ // Setup Controller Wi-Fi
+ public int sysWiFiSetup(final String sSSID, final String sPassword) {
+ return sysWiFiSetup(sSSID, sPassword, WLAN_SEC_UNSEC, WLAN_CIPHER_NOT_SET);
+ }
+
+ public int sysWiFiSetup(final String sSSID, final String sPassword, final int nAuth) {
+ return sysWiFiSetup(sSSID, sPassword, nAuth, WLAN_CIPHER_NOT_SET);
+ }
+
+ public int sysWiFiSetup(final String sSSID, final String sPassword, final int nAuth, final int nCipher) {
+ if( isBLEOK() ) {
+ return bleBridge.SysSetupWiFi(sSSID, sPassword, nAuth, nCipher);
+ }
+ return -1;
+ }
+
+ public int sysQueryCoreID() {
+ if( isBLEOK() ) {
+ return bleBridge.SysQueryCoreID();
+ }
+ return -1;
+ }
+
+ // Serial Console 'set' Command, where sCmd can be any valid 'set' command substring, e.g.
+ /// base 1
+ /// cloud 0
+ /// maindev 1
+ public int sysConfig(final String sCmd) {
+ if( isBLEOK() ) {
+ return bleBridge.SysConfig(sCmd);
+ }
+ return -1;
+ }
+
+ // Serial Console 'sys' Command, where sCmd can be any valid 'sys' command substring, e.g.
+ /// reset
+ /// safe
+ /// dfu
+ /// update
+ /// clear nodeid 1
+ /// clear credientials
+ public int sysControl(final String sCmd) {
+ if( isBLEOK() ) {
+ return bleBridge.SysControl(sCmd);
+ }
+ return -1;
+ }
+
+ //-------------------------------------------------------------------------
+ // Event Handler Interfaces
+ //-------------------------------------------------------------------------
+ public boolean getEnableEventBroadcast() {
+ return m_enableEventBroadcast;
+ }
+
+ public boolean getEnableEventSendMessage() {
+ return m_enableEventSendMessage;
+ }
+
+ public void setEnableEventBroadcast(final boolean flag) {
+ m_enableEventBroadcast = flag;
+ }
+
+ public void setEnableEventSendMessage(final boolean flag) {
+ m_enableEventSendMessage = flag;
+ }
+
+ public void onBridgeFunctionAck(final int result, final int type, final String data) {
+ if( m_bcsHandler != null ) {
+ m_bcsHandler.obtainMessage(BCS_FUNCTION_ACK, result, type, data).sendToTarget();
+ }
+ }
+
+ public void onBridgeCoreID(final String data) {
+ if( m_bcsHandler != null ) {
+ m_bcsHandler.obtainMessage(BCS_FUNCTION_COREID, -1, -1, data).sendToTarget();
+ }
+ }
+
+ public void onBridgeStatusChanged(final BridgeType bridge, final int bcStatus) {
+ if( m_bcsHandler != null ) {
+ m_bcsHandler.obtainMessage(bcStatus, -1, -1, bridge.name()).sendToTarget();
+ }
+ }
+
+ public int addDeviceEventHandler(final Handler handler) {
+ m_lstEH_DevST.add(handler);
+ return m_lstEH_DevST.size();
+ }
+
+ public int addDataEventHandler(final Handler handler) {
+ m_lstEH_SenDT.add(handler);
+ return m_lstEH_SenDT.size();
+ }
+
+ public boolean removeDeviceEventHandler(final Handler handler) {
+ return m_lstEH_DevST.remove(handler);
+ }
+
+ public boolean removeDataEventHandler(final Handler handler) {
+ return m_lstEH_SenDT.remove(handler);
+ }
+
+ public void clearDeviceEventHandlerList() {
+ m_lstEH_DevST.clear();
+ }
+
+ public void clearDataEventHandlerList() {
+ m_lstEH_SenDT.clear();
+ }
+
+ // Send device status message to each handler
+ public void sendDeviceStatusMessage(final Bundle data) {
+ Handler handler;
+ Message msg;
+ for (int i = 0; i < m_lstEH_DevST.size(); i++) {
+ handler = m_lstEH_DevST.get(i);
+ if( handler != null ) {
+ msg = handler.obtainMessage();
+ if( msg != null ) {
+ msg.setData(data);
+ handler.sendMessage(msg);
+ }
+ }
+ }
+ }
+
+ // Send sensor data message to each handler
+ public void sendSensorDataMessage(final Bundle data) {
+ Handler handler;
+ Message msg;
+ for (int i = 0; i < m_lstEH_SenDT.size(); i++) {
+ handler = m_lstEH_SenDT.get(i);
+ if( handler != null ) {
+ msg = handler.obtainMessage();
+ if( msg != null ) {
+ msg.setData(data);
+ handler.sendMessage(msg);
+ }
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Device Manipulate Interfaces (DMI)
+ //-------------------------------------------------------------------------
+ public int sceAddScenario(final int scenarioId, final int br, final int cw, final int ww, final int r, final int g, final int b, final int filter) {
+ int rc = -1;
+
+ // Can only use Cloud Bridge
+ if( isCloudOK() ) {
+ rc = cldBridge.JSONConfigScenario(scenarioId, br, cw, ww, r, g, b, filter);
+ }
+ return rc;
+ }
+
+ public int sceAddSchedule(final int scheduleId, final boolean isRepeat, final String weekdays, final int hour, final int minute, final int alarmId) {
+ int rc = -1;
+
+ // Can only use Cloud Bridge
+ if( isCloudOK() ) {
+ rc = cldBridge.JSONConfigSchudle(scheduleId, isRepeat, weekdays, hour, minute, alarmId);
+ }
+ return rc;
+ }
+
+ public int sceAddRule(final int ruleId, final int scheduleId, final int scenarioId) {
+ int rc = -1;
+
+ // Can only use Cloud Bridge
+ if( isCloudOK() ) {
+ rc = cldBridge.JSONConfigRule(ruleId, scheduleId, scenarioId);
+ }
+ return rc;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/Tools/DataReceiver.java b/app/src/main/java/ca/xlight/demoapp/Tools/DataReceiver.java
new file mode 100644
index 0000000..7cba3a1
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/Tools/DataReceiver.java
@@ -0,0 +1,20 @@
+package ca.xlight.demoapp.Tools;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class DataReceiver extends BroadcastReceiver {
+ private final String TAG = DataReceiver.class.getSimpleName();
+
+ public DataReceiver() {
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: This method is called when the BroadcastReceiver is receiving
+ // an Intent broadcast.
+ Log.i(TAG, "INTENT RECEIVED by " + TAG);
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/Tools/StatusReceiver.java b/app/src/main/java/ca/xlight/demoapp/Tools/StatusReceiver.java
new file mode 100644
index 0000000..82eb8a9
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/Tools/StatusReceiver.java
@@ -0,0 +1,20 @@
+package ca.xlight.demoapp.Tools;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class StatusReceiver extends BroadcastReceiver {
+ private final String TAG = DataReceiver.class.getSimpleName();
+
+ public StatusReceiver() {
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: This method is called when the BroadcastReceiver is receiving
+ // an Intent broadcast.
+ Log.i(TAG, "INTENT RECEIVED by " + TAG);
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/control/ControlFragment.java b/app/src/main/java/ca/xlight/demoapp/control/ControlFragment.java
new file mode 100644
index 0000000..3656f42
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/control/ControlFragment.java
@@ -0,0 +1,527 @@
+package ca.xlight.demoapp.control;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.Tools.StatusReceiver;
+import ca.xlight.demoapp.main.MainActivity;
+import ca.xlight.demoapp.scenario.ScenarioFragment;
+
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import me.priyesh.chroma.ChromaDialog;
+import me.priyesh.chroma.ColorMode;
+import me.priyesh.chroma.ColorSelectListener;
+
+/**
+ * Created by Umar Bhutta.
+ */
+public class ControlFragment extends Fragment {
+ private static final String TAG = ControlFragment.class.getSimpleName();
+
+ private static final String DEFAULT_LAMP_TEXT = "LIVING ROOM";
+ private static final String RINGALL_TEXT = "ALL RINGS";
+ private static final String RING1_TEXT = "RING 1";
+ private static final String RING2_TEXT = "RING 2";
+ private static final String RING3_TEXT = "RING 3";
+
+ private Switch powerSwitch;
+ private SeekBar brightnessSeekBar;
+ private SeekBar cctSeekBar;
+ private TextView colorTextView;
+ private Spinner scenarioSpinner;
+ private LinearLayout scenarioNoneLL;
+ private ToggleButton ring1Button, ring2Button, ring3Button;
+ private TextView deviceRingLabel, powerLabel, brightnessLabel, cctLabel, colorLabel;
+ private ImageView lightImageView;
+
+ private ArrayList scenarioDropdown;
+
+ private String colorHex;
+ private boolean state = false;
+ boolean ring1 = false, ring2 = false, ring3 = false;
+
+ private Handler m_handlerControl;
+
+ private Timer mTimer = null;
+ private TimerTask mTimerTask = null;
+ private static int count = 0;
+ private boolean isPause = false;
+ private boolean isStop = true;
+ private int ran_r = 125, ran_g = 50, ran_b = 0;
+
+ private class MyStatusReceiver extends StatusReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ powerSwitch.setChecked(MainActivity.m_mainDevice.getState() > 0);
+ brightnessSeekBar.setProgress(MainActivity.m_mainDevice.getBrightness());
+ cctSeekBar.setProgress(MainActivity.m_mainDevice.getCCT() - 2700);
+ }
+ }
+ private final MyStatusReceiver m_StatusReceiver = new MyStatusReceiver();
+
+ @Override
+ public void onDestroyView() {
+ if (!isStop) {
+ stopTimer();
+ }
+
+ MainActivity.m_mainDevice.removeDeviceEventHandler(m_handlerControl);
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ getContext().unregisterReceiver(m_StatusReceiver);
+ }
+ super.onDestroyView();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.fragment_control, container, false);
+
+ scenarioDropdown = new ArrayList<>(ScenarioFragment.name);
+ scenarioDropdown.add(0, "None");
+
+ powerSwitch = (Switch) view.findViewById(R.id.powerSwitch);
+ brightnessSeekBar = (SeekBar) view.findViewById(R.id.brightnessSeekBar);
+ cctSeekBar = (SeekBar) view.findViewById(R.id.cctSeekBar);
+ cctSeekBar.setMax(6500-2700);
+ colorTextView = (TextView) view.findViewById(R.id.colorTextView);
+ scenarioNoneLL = (LinearLayout) view.findViewById(R.id.scenarioNoneLL);
+ scenarioNoneLL.setAlpha(1);
+ ring1Button = (ToggleButton) view.findViewById(R.id.ring1Button);
+ ring2Button = (ToggleButton) view.findViewById(R.id.ring2Button);
+ ring3Button = (ToggleButton) view.findViewById(R.id.ring3Button);
+ deviceRingLabel = (TextView) view.findViewById(R.id.deviceRingLabel);
+ brightnessLabel = (TextView) view.findViewById(R.id.brightnessLabel);
+ cctLabel = (TextView) view.findViewById(R.id.cctLabel);
+ powerLabel = (TextView) view.findViewById(R.id.powerLabel);
+ colorLabel = (TextView) view.findViewById(R.id.colorLabel);
+ lightImageView = (ImageView) view.findViewById(R.id.lightImageView);
+
+ scenarioSpinner = (Spinner) view.findViewById(R.id.scenarioSpinner);
+ // Create an ArrayAdapter using the string array and a default spinner layout
+ ArrayAdapter scenarioAdapter = new ArrayAdapter<>(getActivity(), R.layout.control_scenario_spinner_item, scenarioDropdown);
+ // Specify the layout to use when the list of choices appears
+ scenarioAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ // Apply the scenarioAdapter to the spinner
+ scenarioSpinner.setAdapter(scenarioAdapter);
+
+ powerSwitch.setChecked(MainActivity.m_mainDevice.getState() > 0);
+ brightnessSeekBar.setProgress(MainActivity.m_mainDevice.getBrightness());
+ cctSeekBar.setProgress(MainActivity.m_mainDevice.getCCT() - 2700);
+
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ IntentFilter intentFilter = new IntentFilter(xltDevice.bciDeviceStatus);
+ intentFilter.setPriority(3);
+ getContext().registerReceiver(m_StatusReceiver, intentFilter);
+ }
+
+ if( MainActivity.m_mainDevice.getEnableEventSendMessage() ) {
+ m_handlerControl = new Handler() {
+ public void handleMessage(Message msg) {
+ int intValue = msg.getData().getInt("State", -255);
+ if (intValue != -255) {
+ powerSwitch.setChecked(intValue > 0);
+ }
+
+ intValue = msg.getData().getInt("BR", -255);
+ if (intValue != -255) {
+ brightnessSeekBar.setProgress(intValue);
+ }
+
+ intValue = msg.getData().getInt("CCT", -255);
+ if (intValue != -255) {
+ cctSeekBar.setProgress(intValue - 2700);
+ }
+ }
+ };
+ MainActivity.m_mainDevice.addDeviceEventHandler(m_handlerControl);
+ updateDeviceRingLabel();
+ }
+
+ powerSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ //check if on or off
+ state = isChecked;
+ //ParticleAdapter.JSONCommandPower(ParticleAdapter.DEFAULT_DEVICE_ID, state);
+ //ParticleAdapter.FastCallPowerSwitch(ParticleAdapter.DEFAULT_DEVICE_ID, state);
+ MainActivity.m_mainDevice.PowerSwitch(state ? xltDevice.STATE_ON : xltDevice.STATE_OFF);
+ }
+ });
+
+ colorTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int initColor;
+ int ringID = xltDevice.RING_ID_ALL;
+ if( ring1 && !ring2 && !ring3 ) ringID = xltDevice.RING_ID_1;
+ if( ring2 && !ring1 && !ring3 ) ringID = xltDevice.RING_ID_2;
+ if( ring3 && !ring1 && !ring2 ) ringID = xltDevice.RING_ID_3;
+ int nNodeID = MainActivity.m_mainDevice.getDeviceID();
+ if( MainActivity.m_mainDevice.getRed(nNodeID, ringID) == 0 && MainActivity.m_mainDevice.getGreen(nNodeID, ringID) == 0 && MainActivity.m_mainDevice.getBlue(nNodeID, ringID) == 0) {
+ initColor = ContextCompat.getColor(getActivity(), R.color.colorAccent);
+ } else {
+ initColor = Color.argb(0xff, MainActivity.m_mainDevice.getRed(nNodeID, ringID), MainActivity.m_mainDevice.getGreen(nNodeID, ringID), MainActivity.m_mainDevice.getBlue(nNodeID, ringID));
+ }
+ Log.e(TAG, "int: " + initColor + " HEX: #" + String.format("%06X", (0xFFFFFF & initColor)));
+ new ChromaDialog.Builder()
+ .initialColor(initColor)
+ .colorMode(ColorMode.RGB) // There's also ARGB and HSV
+ .onColorSelected(new ColorSelectListener() {
+ @Override
+ public void onColorSelected(int color) {
+ colorHex = String.format("%06X", (0xFFFFFF & color));
+ Log.e(TAG, "int: " + color + " HEX: #" + colorHex);
+
+ state = powerSwitch.isChecked();
+ int br = brightnessSeekBar.getProgress();
+ int nNodeID = MainActivity.m_mainDevice.getDeviceID();
+ //int ww = (cctSeekBar.getProgress() / ((6500 - 2700) * 255));
+ int ww = 0;
+ int r = (color >> 16) & 0xFF;
+ int g = (color >> 8) & 0xFF;
+ int b = (color >> 0) & 0xFF;
+ Log.e(TAG, "RGB: " + r + "," + g + "," + b);
+
+ colorHex = "#" + colorHex;
+ colorTextView.setText(colorHex);
+ colorTextView.setTextColor(Color.parseColor(colorHex));
+
+ //send message to Particle based on which rings have been selected
+ if ((ring1 && ring2 && ring3) || (!ring1 && !ring2 && !ring3)) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_ALL, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_ALL, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(ww);
+ MainActivity.m_mainDevice.setRed(r);
+ MainActivity.m_mainDevice.setGreen(g);
+ MainActivity.m_mainDevice.setBlue(b);
+ } else if (ring1 && ring2) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_1, state, br, ww, r, g, b);
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_2, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_1, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_2, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_1, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_1, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_1, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_1, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_2, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_2, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_2, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_2, b);
+ } else if (ring2 && ring3) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_2, state, br, ww, r, g, b);
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_2, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_2, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_2, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_2, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_2, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_3, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_3, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_3, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_3, b);
+
+ } else if (ring1 && ring3) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_1, state, br, ww, r, g, b);
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_1, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_1, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_1, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_1, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_1, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_3, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_3, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_3, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_3, b);
+ } else if (ring1) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_1, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_1, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_1, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_1, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_1, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_1, b);
+ } else if (ring2) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_2, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_2, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_2, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_2, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_2, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_2, b);
+ } else if (ring3) {
+ //ParticleAdapter.JSONCommandColor(ParticleAdapter.DEFAULT_DEVICE_ID, ParticleAdapter.RING_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_3, state, br, ww, r, g, b);
+ MainActivity.m_mainDevice.setWhite(nNodeID, xltDevice.RING_ID_3, ww);
+ MainActivity.m_mainDevice.setRed(nNodeID, xltDevice.RING_ID_3, r);
+ MainActivity.m_mainDevice.setGreen(nNodeID, xltDevice.RING_ID_3, g);
+ MainActivity.m_mainDevice.setBlue(nNodeID, xltDevice.RING_ID_3, b);
+ } else {
+ //do nothing
+ }
+ }
+ })
+ .create()
+ .show(getFragmentManager(), "dialog");
+ }
+ });
+
+ brightnessSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ Log.e(TAG, "The brightness value is " + seekBar.getProgress());
+ //ParticleAdapter.JSONCommandBrightness(ParticleAdapter.DEFAULT_DEVICE_ID, seekBar.getProgress());
+ MainActivity.m_mainDevice.ChangeBrightness(seekBar.getProgress());
+ }
+ });
+
+ cctSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ Log.d(TAG, "The CCT value is " + seekBar.getProgress()+2700);
+ //ParticleAdapter.JSONCommandCCT(ParticleAdapter.DEFAULT_DEVICE_ID, seekBar.getProgress()+2700);
+ MainActivity.m_mainDevice.ChangeCCT(seekBar.getProgress() + 2700);
+ }
+ });
+
+ scenarioSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+
+ if (!isStop) {
+ stopTimer();
+ }
+
+ if (parent.getItemAtPosition(position).toString() == "None") {
+ //scenarioNoneLL.animate().alpha(1).setDuration(600).start();
+
+ // Clear Special Effect
+ MainActivity.m_mainDevice.SetSpecialEffect(xltDevice.FILTER_SP_EF_NONE);
+
+ //enable all views below spinner
+ disableEnableControls(true);
+ } else {
+ //if anything but "None" is selected, fade scenarioNoneLL out
+ //scenarioNoneLL.animate().alpha(0).setDuration(500).start();
+
+ //disable all views below spinner
+ disableEnableControls(false);
+
+ //ParticleAdapter.JSONCommandScenario(ParticleAdapter.DEFAULT_DEVICE_ID, position);
+ //position passed into above function corresponds to the scenarioId i.e. s1, s2, s3 to trigger
+ //MainActivity.m_mainDevice.ChangeScenario(position);
+
+ // For demonstration
+ if (parent.getItemAtPosition(position).toString() == "Dinner") {
+ if( MainActivity.m_mainDevice.isSunny() ) {
+ MainActivity.m_mainDevice.ChangeScenario(1);
+ } else {
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_ALL, true, 70, 197, 136, 33, 0);
+ }
+ } else if (parent.getItemAtPosition(position).toString() == "Sleep") {
+ if( MainActivity.m_mainDevice.isSunny() ) {
+ MainActivity.m_mainDevice.ChangeScenario(2);
+ } else {
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_ALL, true, 10, 26, 254, 52, 0);
+ }
+ } else if (parent.getItemAtPosition(position).toString() == "Breathe") {
+ MainActivity.m_mainDevice.SetSpecialEffect(xltDevice.FILTER_SP_EF_BREATH);
+ } else if (parent.getItemAtPosition(position).toString() == "Dance") {
+ //if (isStop) {
+ // startTimer();
+ //}
+ MainActivity.m_mainDevice.SetSpecialEffect(xltDevice.FILTER_SP_EF_FAST_FLORID);
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+
+ }
+ });
+
+ ring1Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ ring1 = isChecked;
+ updateDeviceRingLabel();
+ }
+ });
+ ring2Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ ring2 = isChecked;
+ updateDeviceRingLabel();
+ }
+ });
+ ring3Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ ring3 = isChecked;
+ updateDeviceRingLabel();
+ }
+ });
+
+ return view;
+ }
+
+ private void disableEnableControls(boolean isEnabled) {
+ powerSwitch.setEnabled(isEnabled);
+ colorTextView.setEnabled(isEnabled);
+ brightnessSeekBar.setEnabled(isEnabled);
+ cctSeekBar.setEnabled(isEnabled);
+
+ int selectColor = R.color.colorAccent, allLabels = R.color.textColorPrimary;
+ if (isEnabled) {
+ selectColor = R.color.colorAccent;
+ allLabels = R.color.textColorPrimary;
+ } else {
+ selectColor = R.color.colorDisabled;
+ allLabels = R.color.colorDisabled;
+ }
+ colorTextView.setTextColor(ContextCompat.getColor(getActivity(), selectColor));
+ powerLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
+ brightnessLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
+ cctLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
+ colorLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
+ }
+
+ private void updateDeviceRingLabel() {
+ String label = MainActivity.m_mainDevice.getDeviceName();
+
+ if (ring1 && ring2 && ring3) {
+ label += ": " + RINGALL_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring123);
+ } else if (!ring1 && !ring2 && !ring3) {
+ label += ": " + RINGALL_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_noring);
+ } else if (ring1 && ring2) {
+ label += ": " + RING1_TEXT + " & " + RING2_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring12);
+ } else if (ring2 && ring3) {
+ label += ": " + RING2_TEXT + " & " + RING3_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring23);
+ } else if (ring1 && ring3) {
+ label += ": " + RING1_TEXT + " & " + RING3_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring13);
+ } else if (ring1) {
+ label += ": " + RING1_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring1);
+ } else if (ring2) {
+ label += ": " + RING2_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring2);
+ } else if (ring3) {
+ label += ": " + RING3_TEXT;
+ lightImageView.setImageResource(R.drawable.aquabg_ring3);
+ } else {
+ label += "";
+ lightImageView.setImageResource(R.drawable.aquabg_noring);
+ }
+
+ deviceRingLabel.setText(label);
+ }
+
+ private void startTimer(){
+ isStop = false;
+ if (mTimer == null) {
+ mTimer = new Timer();
+ }
+
+ if (mTimerTask == null) {
+ mTimerTask = new TimerTask() {
+ @Override
+ public void run() {
+ Log.i(TAG, "count: "+String.valueOf(count));
+
+ int which;
+ Random random = new Random();
+ do {
+ try {
+ which = random.nextInt(3);
+ if( which == 0 ) {
+ //r = random.nextInt(256);
+ ran_r += random.nextInt(60);
+ ran_r %= 255;
+ } else if( which == 1 ) {
+ //g = random.nextInt(256);
+ ran_g += random.nextInt(45);
+ ran_g %= 255;
+ } else {
+ //b = random.nextInt(256);
+ ran_b += random.nextInt(36);
+ ran_b %= 255;
+ }
+ MainActivity.m_mainDevice.ChangeColor(xltDevice.RING_ID_ALL, true, 10, 0, ran_r, ran_g, ran_b);
+ Thread.sleep(2500);
+ } catch (InterruptedException e) {
+ }
+ } while (isPause);
+
+ count ++;
+ }
+ };
+ }
+
+ if(mTimer != null && mTimerTask != null )
+ mTimer.schedule(mTimerTask, 1000, 1000);
+
+ }
+
+ private void stopTimer(){
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer = null;
+ }
+ if (mTimerTask != null) {
+ mTimerTask.cancel();
+ mTimerTask = null;
+ }
+ count = 0;
+ isStop = true;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/control/DevicesListAdapter.java b/app/src/main/java/ca/xlight/demoapp/control/DevicesListAdapter.java
new file mode 100644
index 0000000..b9578dc
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/control/DevicesListAdapter.java
@@ -0,0 +1,267 @@
+package ca.xlight.demoapp.control;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.Tools.StatusReceiver;
+import ca.xlight.demoapp.glance.AddNewDevice;
+import ca.xlight.demoapp.glance.GlanceFragment;
+import ca.xlight.demoapp.main.MainActivity;
+import ca.xlight.demoapp.R;
+
+/**
+ * Created by Umar Bhutta.
+ */
+public class DevicesListAdapter extends RecyclerView.Adapter {
+
+ private Handler m_handlerDeviceList;
+ private ArrayList m_Switch = new ArrayList<>();
+ private ArrayList m_Icon = new ArrayList<>();
+
+ public int findPositionByNodeID(final int _nodeID) {
+ for (int iSw = 0; iSw < m_Switch.size(); iSw++) {
+ if((Integer)m_Switch.get(iSw).getTag() == _nodeID) {
+ return iSw;
+ }
+ }
+ return -1;
+ }
+
+ public void setSwitchState(final int _nodeID, final int _state, final boolean _alive, final boolean _isSwitch) {
+ int nPos = findPositionByNodeID(_nodeID);
+ if( nPos >= 0 ) {
+ m_Switch.get(nPos).setChecked(_state > 0);
+ if (_isSwitch) {
+ if (_alive) {
+ m_Icon.get(nPos).setImageResource(_state > 0 ? R.drawable.ic_radio_button_checked_green_24dp : R.drawable.ic_radio_button_checked_red_24dp);
+ } else {
+ m_Icon.get(nPos).setImageResource(R.drawable.ic_radio_button_checked_grey_24dp);
+ }
+ } else {
+ if (_alive) {
+ m_Icon.get(nPos).setImageResource(_state > 0 ? R.drawable.ic_lightbulb_outline_green_24dp : R.drawable.ic_lightbulb_outline_black_24dp);
+ } else {
+ m_Icon.get(nPos).setImageResource(R.drawable.ic_lightbulb_outline_grey_24dp);
+ }
+ }
+ }
+ }
+
+ private class MyStatusReceiver extends StatusReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int nNodeID = intent.getIntExtra("nd", -1);
+ if( nNodeID >= 0 ) {
+ int nState;
+ int nType = MainActivity.m_mainDevice.getDeviceType(nNodeID);
+ if ( MainActivity.m_mainDevice.isSwitch(nType) ) {
+ nState = MainActivity.m_mainDevice.getKMState(nNodeID);
+ } else {
+ nState = MainActivity.m_mainDevice.getState(nNodeID);
+ }
+ boolean bAlive = MainActivity.m_mainDevice.getNodeAlive(nNodeID);
+ setSwitchState(nNodeID, nState, bAlive, MainActivity.m_mainDevice.isSwitch(nType));
+ }
+ }
+ }
+ private final MyStatusReceiver m_StatusReceiver = new MyStatusReceiver();
+
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ if( MainActivity.m_mainDevice.getEnableEventSendMessage() ) {
+ m_handlerDeviceList = new Handler() {
+ public void handleMessage(Message msg) {
+ int nNodeID = msg.getData().getInt("nd", -1);
+ if( nNodeID >= 0 ) {
+ int nState;
+ int nType = MainActivity.m_mainDevice.getDeviceType(nNodeID);
+ if ( MainActivity.m_mainDevice.isSwitch(nType) ) {
+ nState = MainActivity.m_mainDevice.getKMState(nNodeID);
+ } else {
+ nState = msg.getData().getInt("State", -255);
+ }
+ boolean bAlive = msg.getData().getBoolean("up", true);
+ setSwitchState(nNodeID, nState, bAlive, MainActivity.m_mainDevice.isSwitch(nType));
+ }
+ }
+ };
+ MainActivity.m_mainDevice.addDeviceEventHandler(m_handlerDeviceList);
+ }
+
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ IntentFilter intentFilter = new IntentFilter(xltDevice.bciDeviceStatus);
+ intentFilter.setPriority(3);
+ recyclerView.getContext().registerReceiver(m_StatusReceiver, intentFilter);
+ }
+ super.onAttachedToRecyclerView(recyclerView);
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.devices_list_item, parent, false);
+ return new DevicesListViewHolder(view);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ if( m_handlerDeviceList != null ) {
+ MainActivity.m_mainDevice.removeDeviceEventHandler(m_handlerDeviceList);
+ }
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ recyclerView.getContext().unregisterReceiver(m_StatusReceiver);
+ }
+ super.onDetachedFromRecyclerView(recyclerView);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ ((DevicesListViewHolder) holder).bindView(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return MainActivity.deviceNodeIDs.size();
+ }
+
+ private class DevicesListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
+ private TextView mDeviceName;
+ private Switch mDeviceSwitch;
+ private ImageView mStatusIcon;
+ private int mDeviceID;
+
+ public DevicesListViewHolder(View itemView) {
+ super(itemView);
+
+ View main = itemView.findViewById(R.id.main);
+ main.setOnClickListener(this);
+ main.setOnLongClickListener(this);
+
+ View mark = itemView.findViewById(R.id.mark);
+ mark.setOnClickListener(this);
+ View delete = itemView.findViewById(R.id.delete);
+ delete.setOnClickListener(this);
+
+ mDeviceName = (TextView) itemView.findViewById(R.id.deviceName);
+ mDeviceSwitch = (Switch) itemView.findViewById(R.id.deviceSwitch);
+ mStatusIcon = (ImageView) itemView.findViewById(R.id.statusIcon);
+
+ itemView.setOnClickListener(this);
+ itemView.setOnLongClickListener(this);
+
+ mDeviceSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ //ParticleAdapter.FastCallPowerSwitch(ParticleAdapter.DEFAULT_DEVICE_ID, isChecked);
+ // Change Current Device/Node
+ MainActivity.m_mainDevice.setDeviceID(mDeviceID);
+ if (MainActivity.m_mainDevice.isSwitch() ) {
+ MainActivity.m_mainDevice.KMSwitch(isChecked, "1");
+ } else {
+ MainActivity.m_mainDevice.PowerSwitch(isChecked ? xltDevice.STATE_ON : xltDevice.STATE_OFF);
+ }
+ }
+ });
+ }
+
+ public void bindView (int position) {
+ mDeviceID = Integer.parseInt(MainActivity.deviceNodeIDs.get(position));
+ mDeviceName.setText(MainActivity.deviceNames.get(position) + ": " + mDeviceID);
+ int nState;
+ int nType = MainActivity.m_mainDevice.getDeviceType(mDeviceID);
+ if ( MainActivity.m_mainDevice.isSwitch(nType) ) {
+ nState = MainActivity.m_mainDevice.getKMState(mDeviceID);
+ } else {
+ nState = MainActivity.m_mainDevice.getState(mDeviceID);
+ }
+ mDeviceSwitch.setChecked(nState > 0);
+ mDeviceSwitch.setTag(mDeviceID);
+ if( m_Switch.size() <= position ) {
+ m_Switch.add(mDeviceSwitch);
+ } else {
+ m_Switch.set(position, mDeviceSwitch);
+ }
+ if( m_Icon.size() <= position ) {
+ m_Icon.add(mStatusIcon);
+ } else {
+ m_Icon.set(position, mStatusIcon);
+ }
+ setSwitchState(mDeviceID, nState, MainActivity.m_mainDevice.getNodeAlive(mDeviceID), MainActivity.m_mainDevice.isSwitch(nType));
+ }
+
+ @Override
+ public void onClick(View v) {
+ int pos;
+ switch (v.getId()) {
+ case R.id.main:
+ MainActivity.m_mainDevice.setDeviceID(mDeviceID);
+ break;
+
+ case R.id.mark:
+ pos = getAdapterPosition();
+ if (GlanceFragment.wndHandler != null) {
+ (GlanceFragment.wndHandler).showDeivceInfoUpdate(MainActivity.deviceNodeIDs.get(pos), MainActivity.deviceNames.get(pos), MainActivity.deviceNodeTypeIDs.get(pos));
+ }
+ notifyItemChanged(pos);
+ break;
+
+ case R.id.delete:
+ pos = getAdapterPosition();
+ MainActivity.m_mainDevice.removeNodeFromDeviceList(Integer.parseInt(MainActivity.deviceNodeIDs.get(pos)));
+ MainActivity.deviceNames.remove(pos);
+ MainActivity.deviceNodeIDs.remove(pos);
+ MainActivity.deviceNodeTypeIDs.remove(pos);
+ notifyItemRemoved(pos);
+ Toast.makeText(v.getContext(), "Device has been removed", Toast.LENGTH_SHORT).show();
+ break;
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ switch (v.getId()) {
+ case R.id.main:
+ // Change Current Device/Node
+ MainActivity.m_mainDevice.setDeviceID(mDeviceID);
+ int nType = MainActivity.m_mainDevice.getDeviceType(mDeviceID);
+ if ( !MainActivity.m_mainDevice.isSwitch(nType) ) {
+ // Bring to Control Activity only if node is not switch device
+ /// ToDo: may bring to multiple switch control screen, so far we only consider single switch
+ /// ToDo: Therefore, we don't need sub-screen.
+ if (MainActivity.m_eventHandler != null) {
+ Message msg = MainActivity.m_eventHandler.obtainMessage();
+ if (msg != null) {
+ Bundle bdlData = new Bundle();
+ bdlData.putInt("cmd", 1); // Menu
+ bdlData.putInt("item", R.id.nav_control); // Item
+ msg.setData(bdlData);
+ MainActivity.m_eventHandler.sendMessage(msg);
+ }
+ }
+ }
+ break;
+ }
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/glance/AddNewDevice.java b/app/src/main/java/ca/xlight/demoapp/glance/AddNewDevice.java
new file mode 100644
index 0000000..6346eb5
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/glance/AddNewDevice.java
@@ -0,0 +1,89 @@
+package ca.xlight.demoapp.glance;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.MainActivity;
+
+public class AddNewDevice extends AppCompatActivity {
+ private TextView m_txtName, m_txtNodeID;
+ private Button m_btnDone;
+ private Spinner m_typeSpinner;
+
+ private String m_deviceName, m_node_id, m_type_id;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_add_new_device);
+
+ TextView m_txtTitle = (TextView) findViewById(R.id.lblTitleDevInfo);
+ m_txtName = (TextView) findViewById(R.id.editDeviceName);
+ m_txtNodeID = (TextView) findViewById(R.id.editNodeID);
+ m_btnDone = (Button) findViewById(R.id.btnAddDone);
+
+ Intent data = getIntent();
+ String incomingID = data.getStringExtra(GlanceFragment.DEVICE_NODE_ID);
+ String w_title, incomingName, incomingType;
+ int selectedType = -1;
+ if (incomingID.length() > 0) {
+ incomingName = data.getStringExtra(GlanceFragment.DEVICE_NAME);
+ incomingType = data.getStringExtra(GlanceFragment.DEVICE_NODE_TYPE);
+ for (int i = 0; i < MainActivity.mDeviceTypeIDs.length; i++ ) {
+ if (MainActivity.mDeviceTypeIDs[i].equalsIgnoreCase(incomingType) ) {
+ selectedType = i;
+ break;
+ }
+ }
+ w_title = "Update Device";
+ m_txtNodeID.setKeyListener(null);
+ } else {
+ incomingName = "";
+ w_title = "Add Device";
+ }
+ m_txtTitle.setText(w_title);
+ m_txtNodeID.setText(incomingID);
+ m_txtName.setText(incomingName);
+
+ //initialize device type spinner
+ m_typeSpinner = (Spinner) findViewById(R.id.DeviceTypeSpinner);
+ // Create an ArrayAdapter using the string array and a default spinner layout
+ ArrayAdapter deviceAdapter = new ArrayAdapter<>(this, R.layout.add_device_type_spinner_item, MainActivity.mDeviceTypes);
+ // Specify the layout to use when the list of choices appears
+ deviceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ // Apply the scenarioAdapter to the spinner
+ m_typeSpinner.setAdapter(deviceAdapter);
+ m_typeSpinner.setSelection(selectedType, true);
+
+ //on click for add button
+ m_btnDone.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ m_deviceName = m_txtName.getText().toString();
+ m_node_id = m_txtNodeID.getText().toString();
+
+ //get value of scenario spinner
+ int index = m_typeSpinner.getSelectedItemPosition();
+ m_type_id = MainActivity.mDeviceTypeIDs[index];
+
+ //send data to update the list
+ Intent returnIntent = getIntent();
+ returnIntent.putExtra(GlanceFragment.DEVICE_NAME, m_deviceName);
+ returnIntent.putExtra(GlanceFragment.DEVICE_NODE_ID, m_node_id);
+ returnIntent.putExtra(GlanceFragment.DEVICE_NODE_TYPE, m_type_id);
+ setResult(Activity.RESULT_OK, returnIntent);
+ finish();
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/glance/GlanceFragment.java b/app/src/main/java/ca/xlight/demoapp/glance/GlanceFragment.java
new file mode 100644
index 0000000..ae172f7
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/glance/GlanceFragment.java
@@ -0,0 +1,403 @@
+package ca.xlight.demoapp.glance;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+import ca.xlight.demoapp.SDK.CloudAccount;
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.Tools.DataReceiver;
+import ca.xlight.demoapp.main.MainActivity;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.SimpleDividerItemDecoration;
+import ca.xlight.demoapp.control.DevicesListAdapter;
+import ca.xlight.demoapp.swipeitemlayout.SwipeItemLayout;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+/**
+ * Created by Umar Bhutta.
+ */
+public class GlanceFragment extends Fragment {
+ private View root;
+ private com.github.clans.fab.FloatingActionButton fab;
+ public static GlanceFragment wndHandler;
+
+ TextView txtLocation, outsideTemp, degreeSymbol, roomTemp, roomHumidity, roomBrightness, outsideHumidity, apparentTemp;
+ ImageView imgWeather;
+
+ public static String DEVICE_NAME = "DEVICE_NAME";
+ public static String DEVICE_NODE_ID = "DEVICE_NODE_ID";
+ public static String DEVICE_NODE_TYPE = "DEVICE_NODE_TYPE";
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+ private DevicesListAdapter devicesListAdapter;
+ private RecyclerView devicesRecyclerView;
+ private WeatherDetails mWeatherDetails;
+
+ private Handler m_handlerGlance;
+
+ private Bitmap icoDefault, icoClearDay, icoClearNight, icoRain, icoSnow, icoSleet, icoWind, icoFog;
+ private Bitmap icoCloudy, icoPartlyCloudyDay, icoPartlyCloudyNight;
+ private static int ICON_WIDTH = 70;
+ private static int ICON_HEIGHT = 75;
+
+ private String strLocation;
+ private double latitude, longitude;
+
+ private class MyDataReceiver extends DataReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ roomTemp.setText(MainActivity.m_mainDevice.m_Data.m_RoomTemp + "\u00B0");
+ roomHumidity.setText(MainActivity.m_mainDevice.m_Data.m_RoomHumidity + "\u0025");
+ roomBrightness.setText(MainActivity.m_mainDevice.m_Data.m_RoomBrightness + "\u0025");
+ }
+ }
+ private final MyDataReceiver m_DataReceiver = new MyDataReceiver();
+
+ @Override
+ public void onDestroyView() {
+ wndHandler = null;
+ devicesRecyclerView.setAdapter(null);
+ MainActivity.m_mainDevice.removeDataEventHandler(m_handlerGlance);
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ getContext().unregisterReceiver(m_DataReceiver);
+ }
+ super.onDestroyView();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ wndHandler = this;
+ root = inflater.inflate(R.layout.fragment_glance, container, false);
+
+ fab = (com.github.clans.fab.FloatingActionButton) root.findViewById(R.id.fab);
+ txtLocation = (TextView) root.findViewById(R.id.location);
+ outsideTemp = (TextView) root.findViewById(R.id.outsideTemp);
+ degreeSymbol = (TextView) root.findViewById(R.id.degreeSymbol);
+ outsideHumidity = (TextView) root.findViewById(R.id.valLocalHumidity);
+ apparentTemp = (TextView) root.findViewById(R.id.valApparentTemp);
+ roomTemp = (TextView) root.findViewById(R.id.valRoomTemp);
+ roomTemp.setText(MainActivity.m_mainDevice.m_Data.m_RoomTemp + "\u00B0");
+ roomHumidity = (TextView) root.findViewById(R.id.valRoomHumidity);
+ roomHumidity.setText(MainActivity.m_mainDevice.m_Data.m_RoomHumidity + "\u0025");
+ roomBrightness = (TextView) root.findViewById(R.id.valRoomBrightness);
+ roomBrightness.setText(MainActivity.m_mainDevice.m_Data.m_RoomBrightness + "\u0025");
+ imgWeather = (ImageView) root.findViewById(R.id.weatherIcon);
+
+ Resources res = getResources();
+ Bitmap weatherIcons = decodeResource(res, R.drawable.weather_icons_1, 420, 600);
+ icoDefault = Bitmap.createBitmap(weatherIcons, 0, 0, ICON_WIDTH, ICON_HEIGHT);
+ icoClearDay = Bitmap.createBitmap(weatherIcons, ICON_WIDTH, 0, ICON_WIDTH, ICON_HEIGHT);
+ icoClearNight = Bitmap.createBitmap(weatherIcons, ICON_WIDTH * 2, 0, ICON_WIDTH, ICON_HEIGHT);
+ icoRain = Bitmap.createBitmap(weatherIcons, ICON_WIDTH * 5, ICON_HEIGHT * 2, ICON_WIDTH, ICON_HEIGHT);
+ icoSnow = Bitmap.createBitmap(weatherIcons, ICON_WIDTH * 4, ICON_HEIGHT * 3, ICON_WIDTH, ICON_HEIGHT);
+ icoSleet = Bitmap.createBitmap(weatherIcons, ICON_WIDTH * 5, ICON_HEIGHT * 3, ICON_WIDTH, ICON_HEIGHT);
+ icoWind = Bitmap.createBitmap(weatherIcons, 0, ICON_HEIGHT * 3, ICON_WIDTH, ICON_HEIGHT);
+ icoFog = Bitmap.createBitmap(weatherIcons, 0, ICON_HEIGHT * 2, ICON_WIDTH, ICON_HEIGHT);
+ icoCloudy = Bitmap.createBitmap(weatherIcons, ICON_WIDTH , ICON_HEIGHT * 5, ICON_WIDTH, ICON_HEIGHT);
+ icoPartlyCloudyDay = Bitmap.createBitmap(weatherIcons, ICON_WIDTH, ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT);
+ icoPartlyCloudyNight = Bitmap.createBitmap(weatherIcons, ICON_WIDTH * 2, ICON_HEIGHT, ICON_WIDTH, ICON_HEIGHT);
+
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onFabPressed(view);
+ }
+ });
+
+ if( MainActivity.m_mainDevice.getEnableEventBroadcast() ) {
+ IntentFilter intentFilter = new IntentFilter(xltDevice.bciSensorData);
+ intentFilter.setPriority(3);
+ getContext().registerReceiver(m_DataReceiver, intentFilter);
+ }
+
+ if( MainActivity.m_mainDevice.getEnableEventSendMessage() ) {
+ m_handlerGlance = new Handler() {
+ public void handleMessage(Message msg) {
+ int intValue = msg.getData().getInt("DHTt", -255);
+ if (intValue != -255) {
+ roomTemp.setText(intValue + "\u00B0");
+ }
+ intValue = msg.getData().getInt("DHTh", -255);
+ if (intValue != -255) {
+ roomHumidity.setText(intValue + "\u0025");
+ }
+ intValue = msg.getData().getInt("ALS", -255);
+ if (intValue != -255) {
+ roomBrightness.setText(intValue + "\u0025");
+ }
+ }
+ };
+ MainActivity.m_mainDevice.addDataEventHandler(m_handlerGlance);
+ }
+
+ //setup recycler view
+ devicesRecyclerView = (RecyclerView) root.findViewById(R.id.devicesRecyclerView);
+ //create list adapter
+ devicesListAdapter = new DevicesListAdapter();
+ //attach adapter to recycler view
+ devicesRecyclerView.setAdapter(devicesListAdapter);
+ //set LayoutManager for recycler view
+ RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
+ //attach LayoutManager to recycler view
+ devicesRecyclerView.setLayoutManager(layoutManager);
+ //divider lines
+ devicesRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getActivity()));
+
+ devicesRecyclerView.addOnItemTouchListener(new SwipeItemLayout.OnSwipeItemTouchListener(getContext()));
+
+ // Get ControllerID
+ int controllerId = getContext().getSharedPreferences(MainActivity.keySettings, Activity.MODE_PRIVATE).getInt(MainActivity.keyControllerID, 0);
+ if( controllerId == 2 ) {
+ // Waterloo
+ strLocation = "Waterloo, ON";
+ latitude = 43.4643;
+ longitude = -80.5204;
+ } else if( controllerId == 3 ) {
+ // Gu'an, Hebei, China
+ strLocation = "Gu An, China";
+ latitude = 39.44;
+ longitude = 116.29;
+
+ } else {
+ // Suzhou
+ strLocation = "Suzhou, China";
+ latitude = 31.2989;
+ longitude = 120.5852;
+ }
+ // Waterloo
+ //final String strLocation = "Waterloo, ON";
+ //double latitude = 43.4643;
+ //double longitude = -80.5204;
+ // Suzhou
+ //final String strLocation = "Suzhou, China";
+ //double latitude = 31.2989;
+ //double longitude = 120.5852;
+ // Gu'an, Hebei, China
+ //final String strLocation = "Gu An, China";
+ //double latitude = 39.44;
+ //double longitude = 116.29;
+
+ String forecastUrl = "https://api.forecast.io/forecast/" + CloudAccount.DarkSky_apiKey + "/" + latitude + "," + longitude;
+
+ if (isNetworkAvailable()) {
+ OkHttpClient client = new OkHttpClient();
+ //build request
+ Request request = new Request.Builder()
+ .url(forecastUrl)
+ .build();
+ //put request in call object to use for returning data
+ Call call = client.newCall(request);
+ //make async call
+ call.enqueue(new Callback() {
+ @Override
+ public void onFailure(Request request, IOException e) {
+
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ try {
+ String jsonData = response.body().string();
+ if (response.isSuccessful()) {
+ mWeatherDetails = getWeatherDetails(jsonData);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ updateDisplay();
+ }
+ });
+ } else {
+ alertUserAboutError();
+ }
+ } catch (IOException | JSONException e) {
+ Log.e(TAG, "Exception caught: " + e);
+ }
+ }
+ });
+ } else {
+ //if network isn't available
+ Toast.makeText(getActivity(), "Please connect to the network before continuing.", Toast.LENGTH_SHORT).show();
+ }
+
+ return root;
+ }
+
+ private void updateDisplay() {
+ imgWeather.setImageBitmap(getWeatherIcon(mWeatherDetails.getIcon()));
+ txtLocation.setText(mWeatherDetails.getLocation());
+ outsideTemp.setText(" " + mWeatherDetails.getTemp("celsius"));
+ degreeSymbol.setText("\u00B0");
+ outsideHumidity.setText(mWeatherDetails.getmHumidity() + "\u0025");
+ apparentTemp.setText(mWeatherDetails.getApparentTemp("celsius") + "\u00B0");
+
+ roomTemp.setText(MainActivity.m_mainDevice.m_Data.m_RoomTemp + "\u00B0");
+ roomHumidity.setText(MainActivity.m_mainDevice.m_Data.m_RoomHumidity + "\u0025");
+ roomBrightness.setText(MainActivity.m_mainDevice.m_Data.m_RoomBrightness + "\u0025");
+ }
+
+ private WeatherDetails getWeatherDetails(String jsonData) throws JSONException {
+ WeatherDetails weatherDetails = new WeatherDetails();
+
+ //make JSONObject for all JSON
+ JSONObject forecast = new JSONObject(jsonData);
+
+ //JSONObject for nested JSONObject inside 'forecast' for current weather details
+ JSONObject currently = forecast.getJSONObject("currently");
+
+ weatherDetails.setLocation(strLocation);
+ weatherDetails.setTemp(currently.getDouble("temperature"));
+ weatherDetails.setIcon(currently.getString("icon"));
+ weatherDetails.setApparentTemp(currently.getDouble("apparentTemperature"));
+ weatherDetails.setHumidity((int)(currently.getDouble("humidity") * 100 + 0.5));
+
+ return weatherDetails;
+ }
+
+ private void alertUserAboutError() {
+ Toast.makeText(getActivity(), "There was an error retrieving weather data.", Toast.LENGTH_SHORT).show();
+ }
+
+ private boolean isNetworkAvailable() {
+ ConnectivityManager manager = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ boolean isAvailable = false;
+
+ //check if network is available and connected to web
+ if (networkInfo != null && networkInfo.isConnected()) {
+ isAvailable = true;
+ }
+
+ return isAvailable;
+ }
+
+ private Bitmap decodeResource(Resources resources, final int id, final int newWidth, final int newHeight) {
+ TypedValue value = new TypedValue();
+ resources.openRawResource(id, value);
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inTargetDensity = value.density;
+ Bitmap loadBmp = BitmapFactory.decodeResource(resources, id, opts);
+
+ int width = loadBmp.getWidth();
+ int height = loadBmp.getHeight();
+
+ float scaleWidth = ((float) newWidth) / width;
+ float scaleHeight = ((float) newHeight) / height;
+
+ Matrix matrix = new Matrix();
+ matrix.postScale(scaleWidth, scaleHeight);
+
+ Bitmap newBmp = Bitmap.createBitmap(loadBmp, 0, 0, width, height, matrix, true);
+ return newBmp;
+ }
+
+ public Bitmap getWeatherIcon(final String iconName) {
+ if( iconName.equalsIgnoreCase("clear-day") ) {
+ return icoClearDay;
+ } else if( iconName.equalsIgnoreCase("clear-night") ) {
+ return icoClearNight;
+ } else if( iconName.equalsIgnoreCase("rain") ) {
+ return icoRain;
+ } else if( iconName.equalsIgnoreCase("snow") ) {
+ return icoSnow;
+ } else if( iconName.equalsIgnoreCase("sleet") ) {
+ return icoSleet;
+ } else if( iconName.equalsIgnoreCase("wind") ) {
+ return icoWind;
+ } else if( iconName.equalsIgnoreCase("fog") ) {
+ return icoFog;
+ } else if( iconName.equalsIgnoreCase("cloudy") ) {
+ return icoCloudy;
+ } else if( iconName.equalsIgnoreCase("partly-cloudy-day") ) {
+ return icoPartlyCloudyDay;
+ } else if( iconName.equalsIgnoreCase("partly-cloudy-night") ) {
+ return icoPartlyCloudyNight;
+ } else {
+ return icoDefault;
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == 1) {
+ if (resultCode == Activity.RESULT_OK) {
+ String incomingName = data.getStringExtra(DEVICE_NAME);
+ String incomingID = data.getStringExtra(DEVICE_NODE_ID);
+ String incomingType = data.getStringExtra(DEVICE_NODE_TYPE);
+
+ int pos = searchDeviceID(incomingID);
+ if( pos >= 0 ) {
+ // Update
+ MainActivity.deviceNames.set(pos, incomingName);
+ MainActivity.deviceNodeTypeIDs.set(pos, incomingType);
+ MainActivity.m_mainDevice.setDeviceType(Integer.parseInt(incomingID), Integer.parseInt(incomingType));
+ MainActivity.m_mainDevice.setDeviceName(Integer.parseInt(incomingID), incomingName);
+ devicesListAdapter.notifyItemChanged(pos);
+ } else {
+ // Add new
+ MainActivity.deviceNames.add(incomingName);
+ MainActivity.deviceNodeIDs.add(incomingID);
+ MainActivity.deviceNodeTypeIDs.add(incomingType);
+ MainActivity.m_mainDevice.addNodeToDeviceList(Integer.parseInt(incomingID), Integer.parseInt(incomingType), incomingName);
+ devicesListAdapter.notifyDataSetChanged();
+ Toast.makeText(getActivity(), "Device has been successfully added", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ }
+
+ private void onFabPressed(View view) {
+ showDeivceInfoUpdate("", "", "");
+ }
+
+ public int searchDeviceID(String nid) {
+ for (int pos = 0; pos < MainActivity.deviceNodeIDs.size(); pos++) {
+ if (MainActivity.deviceNodeIDs.get(pos).equalsIgnoreCase(nid) ) return pos;
+ }
+ return -1;
+ }
+
+ public void showDeivceInfoUpdate(String nid, String name, String type) {
+ Intent intent = new Intent(getContext(), AddNewDevice.class);
+ intent.putExtra(DEVICE_NODE_ID, nid);
+ intent.putExtra(DEVICE_NAME, name);
+ intent.putExtra(DEVICE_NODE_TYPE, type);
+ startActivityForResult(intent, 1);
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/glance/WeatherDetails.java b/app/src/main/java/ca/xlight/demoapp/glance/WeatherDetails.java
new file mode 100644
index 0000000..089adf5
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/glance/WeatherDetails.java
@@ -0,0 +1,73 @@
+package ca.xlight.demoapp.glance;
+
+import android.graphics.Bitmap;
+
+/**
+ * Created by Umar Bhutta.
+ */
+public class WeatherDetails {
+ private String mLocation;
+ private String mIcon;
+ private double mTempF;
+ private int mTempC;
+ private double mApparentTempF;
+ private int mApparentTempC;
+ private int mHumidity;
+
+ public WeatherDetails() {
+ super();
+ }
+
+ public String getLocation() {
+ return mLocation;
+ }
+
+ public void setLocation(final String location) {
+ mLocation = location;
+ }
+
+ public String getIcon() {
+ return mIcon;
+ }
+
+ public void setIcon(final String mIcon) {
+ this.mIcon = mIcon;
+ }
+
+ public int getTemp(final String unit)
+ {
+ if (unit == "fahrenheit") {
+ return (int) mTempF;
+ } else {
+ return mTempC;
+ }
+ }
+
+ public void setTemp(final double mTemp) {
+ this.mTempF = mTemp;
+
+ mTempC = (int) ((mTempF - 32.0) * (5.0/9.0) + 0.5);
+ }
+
+ public int getmHumidity() {
+ return mHumidity;
+ }
+
+ public void setHumidity(final int humidity) {
+ this.mHumidity = humidity;
+ }
+
+ public int getApparentTemp(final String unit)
+ {
+ if (unit == "fahrenheit") {
+ return (int) mApparentTempF;
+ } else {
+ return mApparentTempC;
+ }
+ }
+
+ public void setApparentTemp(final double mTemp) {
+ mApparentTempF = mTemp;
+ mApparentTempC = (int) ((mTempF - 32.0) * (5.0/9.0) + 0.5);
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/main/MainActivity.java b/app/src/main/java/ca/xlight/demoapp/main/MainActivity.java
new file mode 100644
index 0000000..b3f6e99
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/main/MainActivity.java
@@ -0,0 +1,351 @@
+package ca.xlight.demoapp.main;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewDebug;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import ca.xlight.demoapp.SDK.BLE.BLEPairedDeviceList;
+import ca.xlight.demoapp.SDK.CloudAccount;
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.control.ControlFragment;
+import ca.xlight.demoapp.glance.GlanceFragment;
+import ca.xlight.demoapp.scenario.ScenarioFragment;
+import ca.xlight.demoapp.schedule.ScheduleFragment;
+import ca.xlight.demoapp.settings.SettingsFragment;
+import ca.xlight.demoapp.settings.WiFiSetupFragment;
+
+public class MainActivity extends AppCompatActivity
+ implements NavigationView.OnNavigationItemSelectedListener {
+
+ //constants for testing lists
+ public static String keySettings = "Settings";
+ public static String keyControllerID = "ControllerID";
+ public static String keyBridgeID = "BridgeID";
+ public static String keyDeviceCount = "DeviceCount";
+ public static String keyDeviceList = "DeviceList";
+
+ private SharedPreferences m_sp;
+ public static int mControllerId, mBridgeId;
+ public static String[] mControllerNames;
+ public static String[] mBridgeNames;
+ public static String[] mWiFiAuthNames;
+ public static String[] mWiFiCipherNames;
+ public static String[] mDeviceTypes;
+ public static String[] mDeviceTypeIDs;
+ public static ArrayList deviceNames = new ArrayList<>();
+ public static ArrayList deviceNodeIDs = new ArrayList<>();
+ public static ArrayList deviceNodeTypeIDs = new ArrayList<>();
+ public static final String[] scheduleTimes = {"10:30 AM", "12:45 PM", "02:00 PM", "06:45 PM", "08:00 PM", "11:30 PM"};
+ public static final String[] scheduleDays = {"Mo Tu We Th Fr", "Every day", "Mo We Th Sa Su", "Tomorrow", "We", "Mo Tu Fr Sa Su"};
+ public static final String[] scenarioNames = {"Brunching", "Guests", "Naptime", "Dinner", "Sunset", "Bedtime"};
+ public static final String[] scenarioDescriptions = {"A red color at 52% brightness", "A blue-green color at 100% brightness", "An amber color at 50% brightness", "Turn off", "A warm-white color at 100% brightness", "A green color at 52% brightness"};
+ public static final String[] filterNames = {"Breathe", "Music Match", "Flash"};
+
+ public static xltDevice m_mainDevice;
+ public static Handler m_eventHandler;
+ public static Handler m_bcsHandler;
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ saveSettings();
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ saveSettings();
+ super.onDestroy();
+ }
+
+ public void saveSettings() {
+ SharedPreferences.Editor editor = m_sp.edit();
+ editor.putInt(keyControllerID, mControllerId);
+ editor.putInt(keyBridgeID, mBridgeId);
+
+ editor.putInt(keyDeviceCount, deviceNames.size());
+ for (int i = 0; i < deviceNames.size(); i++) {
+ String item = deviceNodeIDs.get(i) + "," + deviceNodeTypeIDs.get(i) + "," + deviceNames.get(i);
+ editor.putString(keyDeviceList + String.valueOf(i), item);
+ }
+
+ editor.commit();
+ }
+
+ public void loadSettings() {
+ m_sp = getSharedPreferences(keySettings, MODE_PRIVATE);
+ mControllerId = m_sp.getInt(keyControllerID, 0);
+ mBridgeId = m_sp.getInt(keyBridgeID, 0);
+
+ deviceNames.clear();
+ deviceNodeIDs.clear();
+ deviceNodeTypeIDs.clear();
+
+ int dev_cnt = m_sp.getInt(keyDeviceCount, 0);
+ if (dev_cnt > 0) {
+ for (int i = 0; i < dev_cnt; i++) {
+ String item = m_sp.getString(keyDeviceList + String.valueOf(i), "");
+ if (item.length() > 4) {
+ String [] temps = item.split(",");
+ if( temps.length >= 3 ) {
+ deviceNodeIDs.add(temps[0]);
+ deviceNodeTypeIDs.add(temps[1]);
+ deviceNames.add(temps[2]);
+ }
+ }
+ }
+ }
+
+ if (deviceNames.size() == 0 ) {
+ deviceNames.add("Living Room");
+ deviceNames.add("Bedroom");
+ deviceNames.add("Dining Room");
+ deviceNodeIDs.add("1");
+ deviceNodeIDs.add("8");
+ deviceNodeIDs.add("9");
+ deviceNodeTypeIDs.add(String.valueOf(xltDevice.DEFAULT_DEVICE_TYPE));
+ deviceNodeTypeIDs.add(String.valueOf(xltDevice.DEFAULT_DEVICE_TYPE));
+ deviceNodeTypeIDs.add(String.valueOf(xltDevice.DEFAULT_DEVICE_TYPE));
+ }
+ }
+
+ public void selectBridge() {
+ boolean bAuto = true;
+
+ if( mBridgeId > 0 ) {
+ if( mBridgeId == 2 ) {
+ if( m_mainDevice.useBridge(xltDevice.BridgeType.BLE) )
+ bAuto = false;
+ } else {
+ if( m_mainDevice.useBridge(xltDevice.BridgeType.Cloud) )
+ bAuto = false;
+ }
+ }
+
+ m_mainDevice.setAutoBridge(bAuto);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ // Load Settings
+ loadSettings();
+
+ m_bcsHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ String bridgeName = (String) msg.obj;
+ switch( msg.what ) {
+ case xltDevice.BCS_CONNECTED:
+ if( bridgeName.equalsIgnoreCase(xltDevice.BridgeType.BLE.name()) || bridgeName.equalsIgnoreCase(xltDevice.BridgeType.Cloud.name()) ) {
+ selectBridge();
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setSubtitle(m_mainDevice.getBridgeInfo());
+ }
+ }
+ break;
+
+ case xltDevice.BCS_NOT_CONNECTED:
+ case xltDevice.BCS_CONNECTION_FAILED:
+ case xltDevice.BCS_CONNECTION_LOST:
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setSubtitle(m_mainDevice.getBridgeInfo());
+ }
+ break;
+
+ case xltDevice.BCS_FUNCTION_ACK:
+ Toast.makeText(getApplicationContext(), (msg.arg1 == 1 ? "OK" : "Failed"), Toast.LENGTH_SHORT).show();
+ break;
+
+ case xltDevice.BCS_FUNCTION_COREID:
+ Toast.makeText(getApplicationContext(), "CoreID: " + bridgeName, Toast.LENGTH_LONG).show();
+ break;
+ }
+ }
+ };
+
+ // Check Bluetooth
+ BLEPairedDeviceList.init(this);
+ if( BLEPairedDeviceList.IsSupported() && !BLEPairedDeviceList.IsEnabled() ) {
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableBtIntent, BLEPairedDeviceList.REQUEST_ENABLE_BT);
+ }
+
+ // Initialize SmartDevice SDK
+ m_mainDevice = new xltDevice();
+ m_mainDevice.Init(this);
+ //m_mainDevice.setBridgePriority(xltDevice.BridgeType.BLE, 9);
+
+ // Setup Device/Node List
+ for( int lv_idx = 0; lv_idx < deviceNodeTypeIDs.size(); lv_idx++ ) {
+ m_mainDevice.addNodeToDeviceList(Integer.parseInt(deviceNodeIDs.get(lv_idx)), Integer.parseInt(deviceNodeTypeIDs.get(lv_idx)), deviceNames.get(lv_idx));
+ }
+ m_mainDevice.setDeviceID(Integer.parseInt(deviceNodeIDs.get(0)));
+
+ // Connect to Controller
+ // Get ControllerID
+ mControllerNames = getResources().getStringArray(R.array.controller_list);
+ mBridgeNames = getResources().getStringArray(R.array.bridge_list);
+ String strControllerID = CloudAccount.DEVICE_ID;
+ if( mControllerId < CloudAccount.DEVICE_IDS.length ) {
+ strControllerID = CloudAccount.DEVICE_IDS[mControllerId];
+ }
+ m_mainDevice.Connect(strControllerID, m_bcsHandler);
+
+ // Set Bridge
+ selectBridge();
+
+ // Set SmartDevice Event Notification Flag
+ //m_mainDevice.setEnableEventSendMessage(false);
+ //m_mainDevice.setEnableEventBroadcast(true);
+
+ mWiFiAuthNames = getResources().getStringArray(R.array.wifi_auth_list);
+ mWiFiCipherNames = getResources().getStringArray(R.array.wifi_cipher_list);
+ mDeviceTypes = getResources().getStringArray(R.array.device_type_list);
+ mDeviceTypeIDs = getResources().getStringArray(R.array.device_type_list_value);
+
+ //setup drawer layout
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawer.setDrawerListener(toggle);
+ toggle.syncState();
+
+ NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+ navigationView.setNavigationItemSelectedListener(this);
+
+ displayView(R.id.nav_glance);
+ navigationView.getMenu().getItem(0).setChecked(true);
+
+ m_eventHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ int nCmd = msg.getData().getInt("cmd", -1);
+ if( nCmd == 1) {
+ // Menu
+ int nItem = msg.getData().getInt("item", -1);
+ if( nItem >= 0 ) {
+ displayView(nItem);
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == BLEPairedDeviceList.REQUEST_ENABLE_BT) {
+ BLEPairedDeviceList.init(this);
+ }
+ }
+
+ public void displayView(int viewId) {
+ Fragment fragment = null;
+ String title = getString(R.string.app_name);
+
+ switch (viewId) {
+ case R.id.nav_glance:
+ fragment = new GlanceFragment();
+ title = "Glance";
+ break;
+ case R.id.nav_control:
+ fragment = new ControlFragment();
+ title = "Control";
+ break;
+ case R.id.nav_schedule:
+ fragment = new ScheduleFragment();
+ title = "Schedule";
+ break;
+ case R.id.nav_scenario:
+ fragment = new ScenarioFragment();
+ title = "Scenario";
+ break;
+ case R.id.nav_settings:
+ fragment = new SettingsFragment();
+ title = "Settings";
+ break;
+ case R.id.nav_wifisetup:
+ fragment = new WiFiSetupFragment();
+ title = "Controller Wi-Fi";
+ break;
+ }
+
+ if (fragment != null) {
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.replace(R.id.placeholder, fragment);
+ ft.commit();
+ }
+
+ // set the toolbar title
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setTitle(title);
+ getSupportActionBar().setSubtitle(m_mainDevice.getBridgeInfo());
+ }
+
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ drawer.closeDrawer(GravityCompat.START);
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ displayView(R.id.nav_settings);
+ return false;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ // Handle navigation view item clicks here.
+ displayView(item.getItemId());
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/main/SimpleDividerItemDecoration.java b/app/src/main/java/ca/xlight/demoapp/main/SimpleDividerItemDecoration.java
similarity index 93%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/main/SimpleDividerItemDecoration.java
rename to app/src/main/java/ca/xlight/demoapp/main/SimpleDividerItemDecoration.java
index e28b817..8b104a2 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/main/SimpleDividerItemDecoration.java
+++ b/app/src/main/java/ca/xlight/demoapp/main/SimpleDividerItemDecoration.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.main;
+package ca.xlight.demoapp.main;
import android.content.Context;
import android.graphics.Canvas;
@@ -7,7 +7,7 @@
import android.support.v7.widget.RecyclerView;
import android.view.View;
-import com.umarbhutta.xlightcompanion.R;
+import ca.xlight.demoapp.R;
/**
* Created by Umar Bhutta.
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/AddScenarioActivity.java b/app/src/main/java/ca/xlight/demoapp/scenario/AddScenarioActivity.java
similarity index 93%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/scenario/AddScenarioActivity.java
rename to app/src/main/java/ca/xlight/demoapp/scenario/AddScenarioActivity.java
index 9789e7d..7cc70ab 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/AddScenarioActivity.java
+++ b/app/src/main/java/ca/xlight/demoapp/scenario/AddScenarioActivity.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.scenario;
+package ca.xlight.demoapp.scenario;
import android.app.Activity;
import android.content.Intent;
@@ -18,8 +18,9 @@
import android.widget.Spinner;
import android.widget.TextView;
-import com.umarbhutta.xlightcompanion.particle.ParticleBridge;
-import com.umarbhutta.xlightcompanion.R;
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.MainActivity;
import me.priyesh.chroma.ChromaDialog;
import me.priyesh.chroma.ColorMode;
@@ -64,7 +65,7 @@ protected void onCreate(Bundle savedInstanceState) {
filterSpinner = (Spinner) findViewById(R.id.filterSpinner);
// Create an ArrayAdapter using the string array and a default spinner layout
- ArrayAdapter filterAdapter = new ArrayAdapter<>(this, R.layout.control_scenario_spinner_item, ParticleBridge.filterNames);
+ ArrayAdapter filterAdapter = new ArrayAdapter<>(this, R.layout.control_scenario_spinner_item, MainActivity.filterNames);
// Specify the layout to use when the list of choices appears
filterAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the scenarioAdapter to the spinner
@@ -124,7 +125,7 @@ public void onClick(View v) {
scenarioInfo = "A " + colorHex + " color with " + scenarioBrightness + "% brightness";
//SEND TO PARTICLE CLOUD FOR ALL RINGS
- ParticleBridge.JSONConfigScenario(scenarioBrightness, cw, ww, r, g, b, scenarioFilter);
+ MainActivity.m_mainDevice.sceAddScenario(ScenarioFragment.name.size(), scenarioBrightness, cw, ww, r, g, b, xltDevice.DEFAULT_FILTER_ID);
//send data to update the list
Intent returnIntent = getIntent();
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioFragment.java b/app/src/main/java/ca/xlight/demoapp/scenario/ScenarioFragment.java
similarity index 90%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioFragment.java
rename to app/src/main/java/ca/xlight/demoapp/scenario/ScenarioFragment.java
index 79706a1..7d1639c 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioFragment.java
+++ b/app/src/main/java/ca/xlight/demoapp/scenario/ScenarioFragment.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.scenario;
+package ca.xlight.demoapp.scenario;
import android.app.Activity;
import android.content.Intent;
@@ -12,8 +12,8 @@
import android.view.ViewGroup;
import android.widget.Toast;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.main.SimpleDividerItemDecoration;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.SimpleDividerItemDecoration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -28,8 +28,8 @@ public class ScenarioFragment extends Fragment {
public static String SCENARIO_NAME = "SCENARIO_NAME";
public static String SCENARIO_INFO = "SCENARIO_INFO";
- public static ArrayList name = new ArrayList<>(Arrays.asList("Preset 1", "Preset 2", "Turn off"));
- public static ArrayList info = new ArrayList<>(Arrays.asList("A bright, party room preset", "A relaxed atmosphere with yellow tones", "Turn the chandelier rings off"));
+ public static ArrayList name = new ArrayList<>(Arrays.asList("Dinner", "Sleep", "Breathe", "Dance"));
+ public static ArrayList info = new ArrayList<>(Arrays.asList("A bright, party room preset", "A relaxed atmosphere with yellow tones", "Breathing light", "Random breathing color"));
ScenarioListAdapter scenarioListAdapter;
RecyclerView scenarioRecyclerView;
@@ -78,8 +78,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
scenarioListAdapter.notifyDataSetChanged();
Toast.makeText(getActivity(), "The scenario has been successfully added", Toast.LENGTH_SHORT).show();
- }
- if (resultCode == Activity.RESULT_CANCELED) {
+ } else if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(getActivity(), "No new scenarios were added to the list", Toast.LENGTH_SHORT).show();
}
}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioListAdapter.java b/app/src/main/java/ca/xlight/demoapp/scenario/ScenarioListAdapter.java
similarity index 92%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioListAdapter.java
rename to app/src/main/java/ca/xlight/demoapp/scenario/ScenarioListAdapter.java
index d6a2f07..ddfaa0f 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/scenario/ScenarioListAdapter.java
+++ b/app/src/main/java/ca/xlight/demoapp/scenario/ScenarioListAdapter.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.scenario;
+package ca.xlight.demoapp.scenario;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -7,8 +7,8 @@
import android.widget.ImageView;
import android.widget.TextView;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.scenario.ScenarioFragment;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.scenario.ScenarioFragment;
/**
* Created by Umar Bhutta.
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/AddScheduleActivity.java b/app/src/main/java/ca/xlight/demoapp/schedule/AddScheduleActivity.java
similarity index 90%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/schedule/AddScheduleActivity.java
rename to app/src/main/java/ca/xlight/demoapp/schedule/AddScheduleActivity.java
index 9445d51..645c26b 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/AddScheduleActivity.java
+++ b/app/src/main/java/ca/xlight/demoapp/schedule/AddScheduleActivity.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.schedule;
+package ca.xlight.demoapp.schedule;
import android.app.Activity;
import android.content.Intent;
@@ -15,9 +15,10 @@
import android.widget.Spinner;
import android.widget.TimePicker;
-import com.umarbhutta.xlightcompanion.particle.ParticleBridge;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.scenario.ScenarioFragment;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.SDK.xltDevice;
+import ca.xlight.demoapp.main.MainActivity;
+import ca.xlight.demoapp.scenario.ScenarioFragment;
import java.util.Calendar;
@@ -31,9 +32,8 @@ public class AddScheduleActivity extends AppCompatActivity {
private Button addButton;
private ImageView backImageView;
- private int defeaultNodeId = ParticleBridge.DEFAULT_DEVICE_ID;
private boolean isRepeat = false;
- private int hour, minute, nodeId;
+ private int hour, minute;
private String am_pm, weekdays, outgoingWeekdays, scenarioName;
//a boolean of which day of week has been selected in active (0-6, 0 = Monday)
private boolean[] weekdaySelected = {false, false, false, false, false, false, false};
@@ -57,8 +57,6 @@ protected void onCreate(Bundle savedInstanceState) {
//initialize all views
timePicker = (TimePicker) findViewById(R.id.timePicker);
isRepeatCheckbox = (CheckBox) findViewById(R.id.isRepeatCheckbox);
- scenarioSpinner = (Spinner) findViewById(R.id.scenarioSpinner);
- deviceSpinner = (Spinner) findViewById(R.id.deviceSpinner);
addButton = (Button) findViewById(R.id.addButton);
backImageView = (ImageView) findViewById(R.id.backImageView);
@@ -70,7 +68,6 @@ protected void onCreate(Bundle savedInstanceState) {
checkboxFriday = (CheckBox) findViewById(R.id.checkboxFriday);
checkboxSaturday = (CheckBox) findViewById(R.id.checkboxSaturday);
-
//initialize scenario spinner
scenarioSpinner = (Spinner) findViewById(R.id.scenarioSpinner);
// Create an ArrayAdapter using the string array and a default spinner layout
@@ -83,7 +80,7 @@ protected void onCreate(Bundle savedInstanceState) {
//initialize device spinner
deviceSpinner = (Spinner) findViewById(R.id.deviceSpinner);
// Create an ArrayAdapter using the string array and a default spinner layout
- ArrayAdapter deviceAdapter = new ArrayAdapter<>(this, R.layout.add_schedule_spinner_item, ParticleBridge.deviceNames);
+ ArrayAdapter deviceAdapter = new ArrayAdapter<>(this, R.layout.add_schedule_spinner_item, MainActivity.deviceNames);
// Specify the layout to use when the list of choices appears
deviceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the scenarioAdapter to the spinner
@@ -180,7 +177,7 @@ public void onClick(View v) {
}
//get value of device spinner
- nodeId = (int) scenarioSpinner.getSelectedItemId();
+ //nodeId = (int) scenarioSpinner.getSelectedItemId();
//get value of scenario spinner
scenarioName = scenarioSpinner.getSelectedItem().toString();
@@ -245,7 +242,18 @@ public void onClick(View v) {
}
//call JSONConfigAlarm to send a schedule row
- ParticleBridge.JSONConfigAlarm(defeaultNodeId, isRepeat, weekdays, hour, minute, scenarioName);
+ // DMI
+ //ParticleAdapter.JSONConfigAlarm(defeaultNodeId, isRepeat, weekdays, hour, minute, scenarioName);
+ int scheduleId = ScheduleFragment.name.size();
+ MainActivity.m_mainDevice.sceAddSchedule(scheduleId, isRepeat, weekdays, hour, minute, xltDevice.DEFAULT_ALARM_ID);
+ // Get scenarioId from name
+ int scenarioId = 1;
+ for (int i = 0; i < ScenarioFragment.name.size(); i++) {
+ if (scenarioName == ScenarioFragment.name.get(i)) {
+ scenarioId = i;
+ }
+ }
+ MainActivity.m_mainDevice.sceAddRule(scheduleId, scheduleId, scenarioId);
//send data to update the list
Intent returnIntent = getIntent();
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleFragment.java b/app/src/main/java/ca/xlight/demoapp/schedule/ScheduleFragment.java
similarity index 95%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleFragment.java
rename to app/src/main/java/ca/xlight/demoapp/schedule/ScheduleFragment.java
index 3f09ce3..20dc962 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleFragment.java
+++ b/app/src/main/java/ca/xlight/demoapp/schedule/ScheduleFragment.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.schedule;
+package ca.xlight.demoapp.schedule;
import android.app.Activity;
@@ -13,8 +13,8 @@
import android.view.ViewGroup;
import android.widget.Toast;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.main.SimpleDividerItemDecoration;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.SimpleDividerItemDecoration;
import java.util.ArrayList;
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleListAdapter.java b/app/src/main/java/ca/xlight/demoapp/schedule/ScheduleListAdapter.java
similarity index 92%
rename from app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleListAdapter.java
rename to app/src/main/java/ca/xlight/demoapp/schedule/ScheduleListAdapter.java
index 72b01ad..ba86fa0 100644
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/schedule/ScheduleListAdapter.java
+++ b/app/src/main/java/ca/xlight/demoapp/schedule/ScheduleListAdapter.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion.schedule;
+package ca.xlight.demoapp.schedule;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -7,8 +7,8 @@
import android.widget.Switch;
import android.widget.TextView;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.schedule.ScheduleFragment;
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.schedule.ScheduleFragment;
/**
* Created by Umar Bhutta.
diff --git a/app/src/main/java/ca/xlight/demoapp/settings/SettingsFragment.java b/app/src/main/java/ca/xlight/demoapp/settings/SettingsFragment.java
new file mode 100644
index 0000000..d0974f3
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/settings/SettingsFragment.java
@@ -0,0 +1,74 @@
+package ca.xlight.demoapp.settings;
+
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.MainActivity;
+
+/**
+ * Created by Umar Bhutta.
+ */
+public class SettingsFragment extends Fragment {
+ RadioGroup m_controllerGroup;
+ RadioGroup m_bridgeGroup;
+ View m_view;
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ m_view = inflater.inflate(R.layout.fragment_settings, container, false);
+
+ m_controllerGroup = (RadioGroup)m_view.findViewById(R.id.controllerList);
+ m_bridgeGroup = (RadioGroup)m_view.findViewById(R.id.bridgeList);
+
+ int i;
+ RadioButton tempButton;
+ for( i = 0; i < MainActivity.mControllerNames.length; i++ ) {
+ tempButton = new RadioButton(getContext());
+ tempButton.setText(MainActivity.mControllerNames[i]);
+ tempButton.setTag(i);
+ m_controllerGroup.addView(tempButton, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ tempButton.setChecked(i == MainActivity.mControllerId);
+ }
+
+ for( i = 0; i < MainActivity.mBridgeNames.length; i++ ) {
+ tempButton = new RadioButton(getContext());
+ tempButton.setText(MainActivity.mBridgeNames[i]);
+ tempButton.setTag(i);
+ m_bridgeGroup.addView(tempButton, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ tempButton.setChecked(i == MainActivity.mBridgeId);
+ }
+
+ m_controllerGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {
+ RadioButton tempButton = (RadioButton)m_view.findViewById(i);
+ MainActivity.mControllerId = (Integer) tempButton.getTag();
+ }
+ });
+
+ m_bridgeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {
+ RadioButton tempButton = (RadioButton)m_view.findViewById(i);
+ MainActivity.mBridgeId = (Integer) tempButton.getTag();
+ }
+ });
+
+ return m_view;
+ }
+}
diff --git a/app/src/main/java/ca/xlight/demoapp/settings/WiFiSetupFragment.java b/app/src/main/java/ca/xlight/demoapp/settings/WiFiSetupFragment.java
new file mode 100644
index 0000000..da5ca83
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/settings/WiFiSetupFragment.java
@@ -0,0 +1,195 @@
+package ca.xlight.demoapp.settings;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import ca.xlight.demoapp.R;
+import ca.xlight.demoapp.main.MainActivity;
+
+/**
+ * Created by sunboss on 2017-06-01.
+ */
+
+public class WiFiSetupFragment extends Fragment {
+ RadioGroup m_authGroup;
+ RadioGroup m_cipherGroup;
+ TextView m_txtSSID, m_txtPassword;
+ Button m_setupWiFi,m_btnClearCredentials, m_btnResetController, m_btnSafe, m_btnDFU;
+ View m_view;
+ int m_nAuth, m_nCipher;
+ private AlertDialog.Builder builder;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ m_view = inflater.inflate(R.layout.fragment_wifisetup, container, false);
+ builder = new AlertDialog.Builder(getContext());
+ builder.setCancelable(true);
+
+ m_authGroup = (RadioGroup)m_view.findViewById(R.id.authList);
+ m_cipherGroup = (RadioGroup)m_view.findViewById(R.id.cipherList);
+ m_setupWiFi = (Button) m_view.findViewById(R.id.btnSetupWiFi);
+ m_btnClearCredentials = (Button) m_view.findViewById(R.id.btnClearCredentials);
+ m_btnResetController = (Button) m_view.findViewById(R.id.btnResetController);
+ m_btnSafe = (Button) m_view.findViewById(R.id.btnSafe);
+ m_btnDFU = (Button) m_view.findViewById(R.id.btnDFU);
+ m_txtSSID = (TextView) m_view.findViewById(R.id.editSSID);
+ m_txtPassword = (TextView) m_view.findViewById(R.id.editPWD);
+ m_nAuth = 0;
+ m_nCipher = 0;
+
+ int i;
+ RadioButton tempButton;
+ for( i = 0; i < MainActivity.mWiFiAuthNames.length; i++ ) {
+ tempButton = new RadioButton(getContext());
+ tempButton.setText(MainActivity.mWiFiAuthNames[i]);
+ tempButton.setTag(i);
+ m_authGroup.addView(tempButton, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ tempButton.setChecked(i == 0);
+ }
+
+ for( i = 0; i < MainActivity.mWiFiCipherNames.length; i++ ) {
+ tempButton = new RadioButton(getContext());
+ tempButton.setText(MainActivity.mWiFiCipherNames[i]);
+ tempButton.setTag(i);
+ m_cipherGroup.addView(tempButton, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ tempButton.setChecked(i == 0);
+ }
+
+ m_authGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {
+ RadioButton tempButton = (RadioButton)m_view.findViewById(i);
+ m_nAuth = (Integer) tempButton.getTag();
+ }
+ });
+
+ m_cipherGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup radioGroup, @IdRes int i) {
+ RadioButton tempButton = (RadioButton)m_view.findViewById(i);
+ m_nCipher = (Integer) tempButton.getTag();
+ }
+ });
+
+ m_setupWiFi.setOnClickListener(new Button.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ if( m_txtSSID.getText().length() > 0 ) {
+ MainActivity.m_mainDevice.sysWiFiSetup(m_txtSSID.getText().toString(), m_txtPassword.getText().toString(), m_nAuth, m_nCipher);
+ }
+ }
+ });
+
+ m_btnClearCredentials.setOnClickListener(new Button.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ builder.setTitle("Warning");
+ builder.setMessage("Are sure you want to clear all Wi-FI credentials on Controller?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ MainActivity.m_mainDevice.sysControl("clear credentials");
+ }
+ });
+
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+ }
+ });
+
+ m_btnResetController.setOnClickListener(new Button.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ builder.setTitle("Warning");
+ builder.setMessage("Are sure you want to reset Controller?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ MainActivity.m_mainDevice.sysControl("reset");
+ }
+ });
+
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+ }
+ });
+
+ m_btnSafe.setOnClickListener(new Button.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ builder.setTitle("Warning");
+ builder.setMessage("Are sure you want to bring Controller to Safe Mode?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ MainActivity.m_mainDevice.sysControl("safe");
+ }
+ });
+
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+ }
+ });
+
+ m_btnDFU.setOnClickListener(new Button.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ builder.setTitle("Warning");
+ builder.setMessage("Are sure you want to bring Controller to DFU Mode?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ MainActivity.m_mainDevice.sysControl("dfu");
+ }
+ });
+
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+ }
+ });
+
+ return m_view;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ca/xlight/demoapp/swipeitemlayout/SwipeItemLayout.java b/app/src/main/java/ca/xlight/demoapp/swipeitemlayout/SwipeItemLayout.java
new file mode 100644
index 0000000..0870e0f
--- /dev/null
+++ b/app/src/main/java/ca/xlight/demoapp/swipeitemlayout/SwipeItemLayout.java
@@ -0,0 +1,775 @@
+package ca.xlight.demoapp.swipeitemlayout;
+
+import android.content.Context;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+/**
+ * Author: liyi
+ * Date: 2017/2/16.
+ */
+public class SwipeItemLayout extends ViewGroup {
+ enum Mode{
+ RESET,DRAG,FLING,TAP
+ }
+ private Mode mTouchMode;
+
+ private ViewGroup mMainView;
+ private ViewGroup mSideView;
+
+ private ScrollRunnable mScrollRunnable;
+ private int mScrollOffset;
+ private int mMaxScrollOffset;
+
+ private boolean mInLayout;
+ private boolean mIsLaidOut;
+
+ public SwipeItemLayout(Context context) {
+ this(context,null);
+ }
+
+ public SwipeItemLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mTouchMode = Mode.RESET;
+ mScrollOffset = 0;
+ mIsLaidOut = false;
+
+ mScrollRunnable = new ScrollRunnable(context);
+ }
+
+ public boolean isOpen(){
+ return mScrollOffset !=0;
+ }
+
+ Mode getTouchMode(){
+ return mTouchMode;
+ }
+
+ void setTouchMode(Mode mode){
+ switch (mTouchMode){
+ case FLING:
+ mScrollRunnable.abort();
+ break;
+ case RESET:
+ break;
+ }
+
+ mTouchMode = mode;
+ }
+
+ public void open(){
+ if(mScrollOffset!=-mMaxScrollOffset){
+ //正在open,不需要处理
+ if(mTouchMode== Mode.FLING && mScrollRunnable.isScrollToLeft())
+ return;
+
+ //当前正在向右滑,abort
+ if(mTouchMode== Mode.FLING /*&& !mScrollRunnable.mScrollToLeft*/)
+ mScrollRunnable.abort();
+
+ mScrollRunnable.startScroll(mScrollOffset,-mMaxScrollOffset);
+ }
+ }
+
+ public void close(){
+ if(mScrollOffset!=0){
+ //正在close,不需要处理
+ if(mTouchMode== Mode.FLING && !mScrollRunnable.isScrollToLeft())
+ return;
+
+ //当前正向左滑,abort
+ if(mTouchMode== Mode.FLING /*&& mScrollRunnable.mScrollToLeft*/)
+ mScrollRunnable.abort();
+
+ mScrollRunnable.startScroll(mScrollOffset,0);
+ }
+ }
+
+ void fling(int xVel){
+ mScrollRunnable.startFling(mScrollOffset,xVel);
+ }
+
+ void revise(){
+ if(mScrollOffset<-mMaxScrollOffset/2)
+ open();
+ else
+ close();
+ }
+
+ boolean trackMotionScroll(int deltaX){
+ if(deltaX==0)
+ return false;
+
+ boolean over = false;
+ int newLeft = mScrollOffset+deltaX;
+ if((deltaX>0 && newLeft>0) || (deltaX<0 && newLeft<-mMaxScrollOffset)){
+ over = true;
+ newLeft = Math.min(newLeft,0);
+ newLeft = Math.max(newLeft,-mMaxScrollOffset);
+ }
+
+ offsetChildrenLeftAndRight(newLeft-mScrollOffset);
+ mScrollOffset = newLeft;
+ return over;
+ }
+
+ private boolean ensureChildren(){
+ int childCount = getChildCount();
+
+ if(childCount!=2)
+ return false;
+
+ View childView = getChildAt(0);
+ if(!(childView instanceof ViewGroup))
+ return false;
+ mMainView = (ViewGroup) childView;
+
+ childView = getChildAt(1);
+ if(!(childView instanceof ViewGroup))
+ return false;
+ mSideView = (ViewGroup) childView;
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if(!ensureChildren())
+ throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ MarginLayoutParams lp = null;
+ int horizontalMargin,verticalMargin;
+ int horizontalPadding = getPaddingLeft()+getPaddingRight();
+ int verticalPadding = getPaddingTop()+getPaddingBottom();
+
+ lp = (MarginLayoutParams) mMainView.getLayoutParams();
+ horizontalMargin = lp.leftMargin+lp.rightMargin;
+ verticalMargin = lp.topMargin+lp.bottomMargin;
+ measureChildWithMargins(mMainView,
+ widthMeasureSpec,horizontalMargin+horizontalPadding,
+ heightMeasureSpec,verticalMargin+verticalPadding);
+
+ if(widthMode==MeasureSpec.AT_MOST)
+ widthSize = Math.min(widthSize,mMainView.getMeasuredWidth()+horizontalMargin+horizontalPadding);
+ else if(widthMode==MeasureSpec.UNSPECIFIED)
+ widthSize = mMainView.getMeasuredWidth()+horizontalMargin+horizontalPadding;
+
+ if(heightMode==MeasureSpec.AT_MOST)
+ heightSize = Math.min(heightSize,mMainView.getMeasuredHeight()+verticalMargin+verticalPadding);
+ else if(heightMode==MeasureSpec.UNSPECIFIED)
+ heightSize = mMainView.getMeasuredHeight()+verticalMargin+verticalPadding;
+
+ setMeasuredDimension(widthSize,heightSize);
+
+ //side layout大小为自身实际大小
+ lp = (MarginLayoutParams) mSideView.getLayoutParams();
+ verticalMargin = lp.topMargin+lp.bottomMargin;
+ mSideView.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight()-verticalMargin-verticalPadding,MeasureSpec.EXACTLY));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if(!ensureChildren())
+ throw new RuntimeException("SwipeItemLayout的子视图不符合规定");
+
+ mInLayout = true;
+
+ int pl = getPaddingLeft();
+ int pt = getPaddingTop();
+ int pr = getPaddingRight();
+ int pb = getPaddingBottom();
+
+ MarginLayoutParams mainLp = (MarginLayoutParams) mMainView.getLayoutParams();
+ MarginLayoutParams sideParams = (MarginLayoutParams) mSideView.getLayoutParams();
+
+ int childLeft = pl+mainLp.leftMargin;
+ int childTop = pt+mainLp.topMargin;
+ int childRight = getWidth()-(pr+mainLp.rightMargin);
+ int childBottom = getHeight()-(mainLp.bottomMargin+pb);
+ mMainView.layout(childLeft,childTop,childRight,childBottom);
+
+ childLeft = childRight+sideParams.leftMargin;
+ childTop = pt+sideParams.topMargin;
+ childRight = childLeft+sideParams.leftMargin+sideParams.rightMargin+mSideView.getMeasuredWidth();
+ childBottom = getHeight()-(sideParams.bottomMargin+pb);
+ mSideView.layout(childLeft,childTop,childRight,childBottom);
+
+ mMaxScrollOffset = mSideView.getWidth()+sideParams.leftMargin+sideParams.rightMargin;
+ mScrollOffset = mScrollOffset<-mMaxScrollOffset/2 ? -mMaxScrollOffset:0;
+
+ offsetChildrenLeftAndRight(mScrollOffset);
+ mInLayout = false;
+ mIsLaidOut = true;
+ }
+
+ void offsetChildrenLeftAndRight(int delta){
+ ViewCompat.offsetLeftAndRight(mMainView,delta);
+ ViewCompat.offsetLeftAndRight(mSideView,delta);
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mInLayout) {
+ super.requestLayout();
+ }
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(LayoutParams p) {
+ return p instanceof MarginLayoutParams ? p : new MarginLayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(LayoutParams p) {
+ return p instanceof MarginLayoutParams && super.checkLayoutParams(p);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if(mScrollOffset!=0 && mIsLaidOut){
+ offsetChildrenLeftAndRight(-mScrollOffset);
+ mScrollOffset = 0;
+ }else
+ mScrollOffset = 0;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if(mScrollOffset!=0 && mIsLaidOut){
+ offsetChildrenLeftAndRight(-mScrollOffset);
+ mScrollOffset = 0;
+ }else
+ mScrollOffset = 0;
+ removeCallbacks(mScrollRunnable);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ //click main view,但是它处于open状态,所以,不需要点击效果,直接拦截不调用click listener
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ View pointView = findTopChildUnder(this,x,y);
+ if(pointView!=null && pointView==mMainView && mScrollOffset !=0)
+ return true;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_CANCEL:
+ break;
+
+ case MotionEvent.ACTION_UP:{
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ View pointView = findTopChildUnder(this,x,y);
+ if(pointView!=null && pointView==mMainView && mTouchMode== Mode.TAP && mScrollOffset !=0)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ //click main view,但是它处于open状态,所以,不需要点击效果,直接拦截不调用click listener
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ View pointView = findTopChildUnder(this,x,y);
+ if(pointView!=null && pointView==mMainView && mScrollOffset !=0)
+ return true;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_CANCEL:
+ break;
+
+ case MotionEvent.ACTION_UP:{
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ View pointView = findTopChildUnder(this,x,y);
+ if(pointView!=null && pointView==mMainView && mTouchMode== Mode.TAP && mScrollOffset !=0) {
+ close();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if(getVisibility()!=View.VISIBLE){
+ mScrollOffset = 0;
+ invalidate();
+ }
+ }
+
+ private static final Interpolator sInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ class ScrollRunnable implements Runnable{
+ private static final int FLING_DURATION = 200;
+ private Scroller mScroller;
+ private boolean mAbort;
+ private int mMinVelocity;
+ private boolean mScrollToLeft;
+
+ ScrollRunnable(Context context){
+ mScroller = new Scroller(context,sInterpolator);
+ mAbort = false;
+ mScrollToLeft = false;
+
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mMinVelocity = configuration.getScaledMinimumFlingVelocity();
+ }
+
+ void startScroll(int startX,int endX){
+ if(startX!=endX){
+ Log.e("scroll - startX - endX",""+startX+" "+endX);
+ setTouchMode(Mode.FLING);
+ mAbort = false;
+ mScrollToLeft = endXmMinVelocity && startX!=0) {
+ startScroll(startX, 0);
+ return;
+ }
+
+ if(xVel<-mMinVelocity && startX!=-mMaxScrollOffset) {
+ startScroll(startX, -mMaxScrollOffset);
+ return;
+ }
+
+ startScroll(startX,startX>-mMaxScrollOffset/2 ? 0:-mMaxScrollOffset);
+ }
+
+ void abort(){
+ if(!mAbort){
+ mAbort = true;
+ if(!mScroller.isFinished()){
+ mScroller.abortAnimation();
+ removeCallbacks(this);
+ }
+ }
+ }
+
+ //是否正在滑动需要另外判断
+ boolean isScrollToLeft(){
+ return mScrollToLeft;
+ }
+
+ @Override
+ public void run() {
+ Log.e("abort",Boolean.toString(mAbort));
+ if(!mAbort){
+ boolean more = mScroller.computeScrollOffset();
+ int curX = mScroller.getCurrX();
+ Log.e("curX",""+curX);
+
+ boolean atEdge = trackMotionScroll(curX-mScrollOffset);
+ if(more && !atEdge) {
+ ViewCompat.postOnAnimation(SwipeItemLayout.this, this);
+ return;
+ }
+
+ if(atEdge){
+ removeCallbacks(this);
+ if(!mScroller.isFinished())
+ mScroller.abortAnimation();
+ setTouchMode(Mode.RESET);
+ }
+
+ if(!more){
+ setTouchMode(Mode.RESET);
+ //绝对不会出现这种意外的!!!可以注释掉
+ if(mScrollOffset!=0){
+ if(Math.abs(mScrollOffset)>mMaxScrollOffset/2)
+ mScrollOffset = -mMaxScrollOffset;
+ else
+ mScrollOffset = 0;
+ ViewCompat.postOnAnimation(SwipeItemLayout.this,this);
+ }
+ }
+ }
+ }
+ }
+
+ public static class OnSwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
+ private SwipeItemLayout mCaptureItem;
+ private float mLastMotionX;
+ private float mLastMotionY;
+ private VelocityTracker mVelocityTracker;
+
+ private int mActivePointerId;
+
+ private int mTouchSlop;
+ private int mMaximumVelocity;
+
+ private boolean mDealByParent;
+ private boolean mIsProbeParent;
+
+ public OnSwipeItemTouchListener(Context context){
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mActivePointerId = -1;
+ mDealByParent = false;
+ mIsProbeParent = false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ if(mIsProbeParent)
+ return false;
+
+ boolean intercept = false;
+ final int action = ev.getActionMasked();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (action){
+ case MotionEvent.ACTION_DOWN:{
+ mActivePointerId = ev.getPointerId(0);
+ final float x = ev.getX();
+ final float y = ev.getY();
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ boolean pointOther = false;
+ SwipeItemLayout pointItem = null;
+ //首先知道ev针对的是哪个item
+ View pointView = findTopChildUnder(rv,(int)x,(int)y);
+ if(pointView==null || !(pointView instanceof SwipeItemLayout)){
+ //可能是head view或bottom view
+ pointOther = true;
+ }else
+ pointItem = (SwipeItemLayout) pointView;
+
+ //此时的pointOther=true,意味着点击的view为空或者点击的不是item
+ //还没有把点击的是item但是不是capture item给过滤出来
+ if(!pointOther && (mCaptureItem==null || mCaptureItem!=pointItem))
+ pointOther = true;
+
+ //点击的是capture item
+ if(!pointOther){
+ Mode touchMode = mCaptureItem.getTouchMode();
+
+ //如果它在fling,就转为drag
+ //需要拦截,并且requestDisallowInterceptTouchEvent
+ boolean disallowIntercept = false;
+ if(touchMode== Mode.FLING){
+ mCaptureItem.setTouchMode(Mode.DRAG);
+ disallowIntercept = true;
+ intercept = true;
+ }else {//如果是expand的,就不允许parent拦截
+ mCaptureItem.setTouchMode(Mode.TAP);
+ if(mCaptureItem.isOpen())
+ disallowIntercept = true;
+ }
+
+ if(disallowIntercept){
+ final ViewParent parent = rv.getParent();
+ if (parent!= null)
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }else{//capture item为null或者与point item不一样
+ //直接将其close掉
+ if(mCaptureItem!=null && mCaptureItem.isOpen()) {
+ mCaptureItem.close();
+ mCaptureItem = null;
+ intercept = true;
+ }
+
+ if(pointItem!=null) {
+ mCaptureItem = pointItem;
+ mCaptureItem.setTouchMode(Mode.TAP);
+ }else
+ mCaptureItem = null;
+ }
+
+ //如果parent处于fling状态,此时,parent就会转为drag。此时,应该将后续move都交给parent处理
+ mIsProbeParent = true;
+ mDealByParent = rv.onInterceptTouchEvent(ev);
+ mIsProbeParent = false;
+ if(mDealByParent)
+ intercept = false;
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int actionIndex = ev.getActionIndex();
+ mActivePointerId = ev.getPointerId(actionIndex);
+
+ mLastMotionX = ev.getX(actionIndex);
+ mLastMotionY = ev.getY(actionIndex);
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int actionIndex = ev.getActionIndex();
+ final int pointerId = ev.getPointerId(actionIndex);
+ if (pointerId == mActivePointerId) {
+ final int newIndex = actionIndex == 0 ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newIndex);
+
+ mLastMotionX = ev.getX(newIndex);
+ mLastMotionY = ev.getY(newIndex);
+ }
+ break;
+ }
+
+ //down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (activePointerIndex == -1)
+ break;
+
+ //在down时,就被认定为parent的drag,所以,直接交给parent处理即可
+ if(mDealByParent) {
+ if(mCaptureItem!=null && mCaptureItem.isOpen())
+ mCaptureItem.close();
+ return false;
+ }
+
+ final int x = (int) (ev.getX(activePointerIndex)+.5f);
+ final int y = (int) ((int) ev.getY(activePointerIndex)+.5f);
+
+ int deltaX = (int) (x - mLastMotionX);
+ int deltaY = (int)(y-mLastMotionY);
+ final int xDiff = Math.abs(deltaX);
+ final int yDiff = Math.abs(deltaY);
+
+ if(mCaptureItem!=null && !mDealByParent){
+ Mode touchMode = mCaptureItem.getTouchMode();
+
+ if(touchMode== Mode.TAP ){
+ //如果capture item是open的,下拉有两种处理方式:
+ // 1、下拉后,直接close item
+ // 2、只要是open的,就拦截所有它的消息,这样如果点击open的,就只能滑动该capture item
+ //网易邮箱,在open的情况下,下拉直接close
+ //QQ,在open的情况下,下拉也是close。但是,做的不够好,没有达到该效果。
+ if(xDiff>mTouchSlop && xDiff>yDiff){
+ mCaptureItem.setTouchMode(Mode.DRAG);
+ final ViewParent parent = rv.getParent();
+ parent.requestDisallowInterceptTouchEvent(true);
+
+ deltaX = deltaX>0 ? deltaX-mTouchSlop:deltaX+mTouchSlop;
+ }else{// if(yDiff>mTouchSlop){
+ mIsProbeParent = true;
+ boolean isParentConsume = rv.onInterceptTouchEvent(ev);
+ mIsProbeParent = false;
+ if(isParentConsume){
+ //表明不是水平滑动,即不判定为SwipeItemLayout的滑动
+ //但是,可能是下拉刷新SwipeRefreshLayout或者RecyclerView的滑动
+ //一般的下拉判定,都是yDiff>mTouchSlop,所以,此处这么写不会出问题
+ //这里这么做以后,如果判定为下拉,就直接close
+ mDealByParent = true;
+ mCaptureItem.close();
+ }
+ }
+ }
+
+ touchMode = mCaptureItem.getTouchMode();
+ if(touchMode== Mode.DRAG){
+ intercept = true;
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ //对capture item进行拖拽
+ mCaptureItem.trackMotionScroll(deltaX);
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ if(mCaptureItem!=null){
+ Mode touchMode = mCaptureItem.getTouchMode();
+ if(touchMode== Mode.DRAG){
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
+ mCaptureItem.fling(xVel);
+
+ intercept = true;
+ }
+ }
+ cancel();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if(mCaptureItem!=null)
+ mCaptureItem.revise();
+ cancel();
+ break;
+ }
+
+ return intercept;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ final int action = ev.getActionMasked();
+ final int actionIndex = ev.getActionIndex();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (action){
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mActivePointerId = ev.getPointerId(actionIndex);
+
+ mLastMotionX = ev.getX(actionIndex);
+ mLastMotionY = ev.getY(actionIndex);
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int pointerId = ev.getPointerId(actionIndex);
+ if(pointerId==mActivePointerId){
+ final int newIndex = actionIndex == 0 ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newIndex);
+
+ mLastMotionX = ev.getX(newIndex);
+ mLastMotionY = ev.getY(newIndex);
+ }
+ break;
+
+ //down时,已经将capture item定下来了。所以,后面可以安心考虑event处理
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (activePointerIndex == -1)
+ break;
+
+ final float x = ev.getX(activePointerIndex);
+ final float y = (int) ev.getY(activePointerIndex);
+
+ int deltaX = (int) (x - mLastMotionX);
+
+ if(mCaptureItem!=null && mCaptureItem.getTouchMode()== Mode.DRAG){
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ //对capture item进行拖拽
+ mCaptureItem.trackMotionScroll(deltaX);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ if(mCaptureItem!=null){
+ Mode touchMode = mCaptureItem.getTouchMode();
+ if(touchMode== Mode.DRAG){
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int xVel = (int) velocityTracker.getXVelocity(mActivePointerId);
+ mCaptureItem.fling(xVel);
+ }
+ }
+ cancel();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if(mCaptureItem!=null)
+ mCaptureItem.revise();
+
+ cancel();
+ break;
+
+ }
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+
+ void cancel(){
+ mDealByParent = false;
+ mActivePointerId = -1;
+ if(mVelocityTracker!=null){
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ }
+
+ static View findTopChildUnder(ViewGroup parent,int x, int y) {
+ final int childCount = parent.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = parent.getChildAt(i);
+ if (x >= child.getLeft() && x < child.getRight()
+ && y >= child.getTop() && y < child.getBottom()) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ public static void closeAllItems(RecyclerView recyclerView){
+ for(int i=0;i scenarioDropdown;
-
- private String colorHex;
- private boolean state = false;
- boolean ring1 = false, ring2 = false, ring3 = false;
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.fragment_control, container, false);
-
- scenarioDropdown = new ArrayList<>(ScenarioFragment.name);
- scenarioDropdown.add(0, "None");
-
- powerSwitch = (Switch) view.findViewById(R.id.powerSwitch);
- brightnessSeekBar = (SeekBar) view.findViewById(R.id.brightnessSeekBar);
- cctSeekBar = (SeekBar) view.findViewById(R.id.cctSeekBar);
- cctSeekBar.setMax(6500-2700);
- colorTextView = (TextView) view.findViewById(R.id.colorTextView);
- scenarioNoneLL = (LinearLayout) view.findViewById(R.id.scenarioNoneLL);
- scenarioNoneLL.setAlpha(1);
- ring1Button = (ToggleButton) view.findViewById(R.id.ring1Button);
- ring2Button = (ToggleButton) view.findViewById(R.id.ring2Button);
- ring3Button = (ToggleButton) view.findViewById(R.id.ring3Button);
- deviceRingLabel = (TextView) view.findViewById(R.id.deviceRingLabel);
- brightnessLabel = (TextView) view.findViewById(R.id.brightnessLabel);
- cctLabel = (TextView) view.findViewById(R.id.cctLabel);
- powerLabel = (TextView) view.findViewById(R.id.powerLabel);
- colorLabel = (TextView) view.findViewById(R.id.colorLabel);
- lightImageView = (ImageView) view.findViewById(R.id.lightImageView);
-
- scenarioSpinner = (Spinner) view.findViewById(R.id.scenarioSpinner);
- // Create an ArrayAdapter using the string array and a default spinner layout
- ArrayAdapter scenarioAdapter = new ArrayAdapter<>(getActivity(), R.layout.control_scenario_spinner_item, scenarioDropdown);
- // Specify the layout to use when the list of choices appears
- scenarioAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- // Apply the scenarioAdapter to the spinner
- scenarioSpinner.setAdapter(scenarioAdapter);
-
- powerSwitch.setChecked(MainActivity.mainDevice_st > 0);
- brightnessSeekBar.setProgress(MainActivity.mainDevice_br);
- cctSeekBar.setProgress(MainActivity.mainDevice_cct - 2700);
-
- MainActivity.handlerControl = new Handler() {
- public void handleMessage(Message msg) {
- int intValue = msg.getData().getInt("State", -255);
- if( intValue != -255 ) {
- powerSwitch.setChecked(intValue > 0);
- }
-
- intValue = msg.getData().getInt("BR", -255);
- if( intValue != -255 ) {
- brightnessSeekBar.setProgress(intValue);
- }
-
- intValue = msg.getData().getInt("CCT", -255);
- if( intValue != -255 ) {
- cctSeekBar.setProgress(intValue - 2700);
- }
- }
- };
-
- powerSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- //check if on or off
- state = isChecked;
- //ParticleBridge.JSONCommandPower(ParticleBridge.DEFAULT_DEVICE_ID, state);
- ParticleBridge.FastCallPowerSwitch(ParticleBridge.DEFAULT_DEVICE_ID, state);
- }
- });
-
- colorTextView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- new ChromaDialog.Builder()
- .initialColor(ContextCompat.getColor(getActivity(), R.color.colorAccent))
- .colorMode(ColorMode.RGB) // There's also ARGB and HSV
- .onColorSelected(new ColorSelectListener() {
- @Override
- public void onColorSelected(int color) {
- Log.e(TAG, "int: " + color);
- colorHex = String.format("%06X", (0xFFFFFF & color));
- Log.e(TAG, "HEX: #" + colorHex);
-
- int cw = 0;
- int ww = 0;
- int c = (int) Long.parseLong(colorHex, 16);
- int r = (c >> 16) & 0xFF;
- int g = (c >> 8) & 0xFF;
- int b = (c >> 0) & 0xFF;
- Log.e(TAG, "RGB: " + r + "," + g + "," + b);
-
- colorHex = "#" + colorHex;
- colorTextView.setText(colorHex);
- colorTextView.setTextColor(Color.parseColor(colorHex));
-
- //send message to Particle based on which rings have been selected
- if ((ring1 && ring2 && ring3) || (!ring1 && !ring2 && !ring3)) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_ALL, state, cw, ww, r, g, b);
- } else if (ring1 && ring2) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_1, state, cw, ww, r, g, b);
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_2, state, cw, ww, r, g, b);
- } else if (ring2 && ring3) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_2, state, cw, ww, r, g, b);
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_3, state, cw, ww, r, g, b);
- } else if (ring1 && ring3) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_1, state, cw, ww, r, g, b);
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_3, state, cw, ww, r, g, b);
- } else if (ring1) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_1, state, cw, ww, r, g, b);
- } else if (ring2) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_2, state, cw, ww, r, g, b);
- } else if (ring3) {
- ParticleBridge.JSONCommandColor(ParticleBridge.DEFAULT_DEVICE_ID, ParticleBridge.RING_3, state, cw, ww, r, g, b);
- } else {
- //do nothing
- }
- }
- })
- .create()
- .show(getFragmentManager(), "dialog");
- }
- });
-
- brightnessSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- Log.e(TAG, "The brightness value is " + seekBar.getProgress());
- ParticleBridge.JSONCommandBrightness(ParticleBridge.DEFAULT_DEVICE_ID, seekBar.getProgress());
- }
- });
-
- cctSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- Log.d(TAG, "The CCT value is " + seekBar.getProgress()+2700);
- ParticleBridge.JSONCommandCCT(ParticleBridge.DEFAULT_DEVICE_ID, seekBar.getProgress()+2700);
- }
- });
-
- scenarioSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- if (parent.getItemAtPosition(position).toString() == "None") {
- //scenarioNoneLL.animate().alpha(1).setDuration(600).start();
-
- //enable all views below spinner
- disableEnableControls(true);
- } else {
- //if anything but "None" is selected, fade scenarioNoneLL out
- //scenarioNoneLL.animate().alpha(0).setDuration(500).start();
-
- //disable all views below spinner
- disableEnableControls(false);
-
- ParticleBridge.JSONCommandScenario(ParticleBridge.DEFAULT_DEVICE_ID, position);
- //position passed into above function corresponds to the scenarioId i.e. s1, s2, s3 to trigger
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {
-
- }
- });
-
- ring1Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- ring1 = isChecked;
- updateDeviceRingLabel();
- }
- });
- ring2Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- ring2 = isChecked;
- updateDeviceRingLabel();
- }
- });
- ring3Button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- ring3 = isChecked;
- updateDeviceRingLabel();
- }
- });
-
- return view;
- }
-
- private void disableEnableControls(boolean isEnabled) {
- powerSwitch.setEnabled(isEnabled);
- colorTextView.setEnabled(isEnabled);
- brightnessSeekBar.setEnabled(isEnabled);
- cctSeekBar.setEnabled(isEnabled);
-
- int selectColor = R.color.colorAccent, allLabels = R.color.textColorPrimary;
- if (isEnabled) {
- selectColor = R.color.colorAccent;
- allLabels = R.color.textColorPrimary;
- } else {
- selectColor = R.color.colorDisabled;
- allLabels = R.color.colorDisabled;
- }
- colorTextView.setTextColor(ContextCompat.getColor(getActivity(), selectColor));
- powerLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
- brightnessLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
- cctLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
- colorLabel.setTextColor(ContextCompat.getColor(getActivity(), allLabels));
- }
-
- private void updateDeviceRingLabel() {
- String label = ParticleBridge.DEFAULT_LAMP_TEXT;
-
- if (ring1 && ring2 && ring3) {
- label += ": " + ParticleBridge.RINGALL_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring123);
- } else if (!ring1 && !ring2 && !ring3) {
- label += ": " + ParticleBridge.RINGALL_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_noring);
- } else if (ring1 && ring2) {
- label += ": " + ParticleBridge.RING1_TEXT + " & " + ParticleBridge.RING2_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring12);
- } else if (ring2 && ring3) {
- label += ": " + ParticleBridge.RING2_TEXT + " & " + ParticleBridge.RING3_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring23);
- } else if (ring1 && ring3) {
- label += ": " + ParticleBridge.RING1_TEXT + " & " + ParticleBridge.RING3_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring13);
- } else if (ring1) {
- label += ": " + ParticleBridge.RING1_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring1);
- } else if (ring2) {
- label += ": " + ParticleBridge.RING2_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring2);
- } else if (ring3) {
- label += ": " + ParticleBridge.RING3_TEXT;
- lightImageView.setImageResource(R.drawable.aquabg_ring3);
- } else {
- label += "";
- lightImageView.setImageResource(R.drawable.aquabg_noring);
- }
-
- deviceRingLabel.setText(label);
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/control/DevicesListAdapter.java b/app/src/main/java/com/umarbhutta/xlightcompanion/control/DevicesListAdapter.java
deleted file mode 100644
index bc6578c..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/control/DevicesListAdapter.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.umarbhutta.xlightcompanion.control;
-
-import android.os.Handler;
-import android.os.Message;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.umarbhutta.xlightcompanion.main.MainActivity;
-import com.umarbhutta.xlightcompanion.particle.ParticleBridge;
-import com.umarbhutta.xlightcompanion.R;
-
-/**
- * Created by Umar Bhutta.
- */
-public class DevicesListAdapter extends RecyclerView.Adapter {
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.devices_list_item, parent, false);
- return new DevicesListViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- ((DevicesListViewHolder) holder).bindView(position);
- }
-
- @Override
- public int getItemCount() {
- return 3;
- }
-
- private class DevicesListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
- private TextView mDeviceName;
- private Switch mDeviceSwitch;
-
- public DevicesListViewHolder(View itemView) {
- super(itemView);
- mDeviceName = (TextView) itemView.findViewById(R.id.deviceName);
- mDeviceSwitch = (Switch) itemView.findViewById(R.id.deviceSwitch);
-
- itemView.setOnClickListener(this);
-
- mDeviceSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- ParticleBridge.FastCallPowerSwitch(ParticleBridge.DEFAULT_DEVICE_ID, isChecked);
- }
- });
- }
-
- public void bindView (int position) {
- mDeviceName.setText(ParticleBridge.deviceNames[position]);
- if (position == 0) {
- // Main device
- mDeviceSwitch.setChecked(MainActivity.mainDevice_st > 0);
- MainActivity.handlerDeviceList = new Handler() {
- public void handleMessage(Message msg) {
- int intValue = msg.getData().getInt("State", -255);
- if( intValue != -255 ) {
- mDeviceSwitch.setChecked(MainActivity.mainDevice_st > 0);
- }
- }
- };
- }
- }
-
- @Override
- public void onClick(View v) {
- }
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/glance/GlanceFragment.java b/app/src/main/java/com/umarbhutta/xlightcompanion/glance/GlanceFragment.java
deleted file mode 100644
index 849de59..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/glance/GlanceFragment.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.umarbhutta.xlightcompanion.glance;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.squareup.okhttp.Call;
-import com.squareup.okhttp.Callback;
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
-import com.umarbhutta.xlightcompanion.main.MainActivity;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.main.SimpleDividerItemDecoration;
-import com.umarbhutta.xlightcompanion.control.DevicesListAdapter;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.IOException;
-
-/**
- * Created by Umar Bhutta.
- */
-public class GlanceFragment extends Fragment {
- private com.github.clans.fab.FloatingActionButton fab;
- TextView outsideTemp, degreeSymbol, roomTemp;
-
- private static final String TAG = MainActivity.class.getSimpleName();
- WeatherDetails mWeatherDetails;
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_glance, container, false);
-
- fab = (com.github.clans.fab.FloatingActionButton) view.findViewById(R.id.fab);
- outsideTemp = (TextView) view.findViewById(R.id.outsideTemp);
- degreeSymbol = (TextView) view.findViewById(R.id.degreeSymbol);
- roomTemp = (TextView) view.findViewById(R.id.valRoomTemp);
- roomTemp.setText(MainActivity.mainRoomTemp + "\u00B0");
-
- MainActivity.handlerGlance = new Handler() {
- public void handleMessage(Message msg) {
- int intValue = msg.getData().getInt("DHTt", -255);
- if( intValue != -255 ) {
- roomTemp.setText(intValue + "\u00B0");
- }
- }
- };
-
- //setup recycler view
- RecyclerView devicesRecyclerView = (RecyclerView) view.findViewById(R.id.devicesRecyclerView);
- //create list adapter
- DevicesListAdapter devicesListAdapter = new DevicesListAdapter();
- //attach adapter to recycler view
- devicesRecyclerView.setAdapter(devicesListAdapter);
- //set LayoutManager for recycler view
- RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
- //attach LayoutManager to recycler view
- devicesRecyclerView.setLayoutManager(layoutManager);
- //divider lines
- devicesRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getActivity()));
-
- String apiKey = "b6756abd11c020e6e9914c9fb4730169";
- double latitude = 43.4643;
- double longitude = -80.5204;
- String forecastUrl = "https://api.forecast.io/forecast/" + apiKey + "/" + latitude + "," + longitude;
-
- if (isNetworkAvailable()) {
- OkHttpClient client = new OkHttpClient();
- //build request
- Request request = new Request.Builder()
- .url(forecastUrl)
- .build();
- //put request in call object to use for returning data
- Call call = client.newCall(request);
- //make async call
- call.enqueue(new Callback() {
- @Override
- public void onFailure(Request request, IOException e) {
-
- }
-
- @Override
- public void onResponse(Response response) throws IOException {
- try {
- String jsonData = response.body().string();
- if (response.isSuccessful()) {
- mWeatherDetails = getWeatherDetails(jsonData);
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- updateDisplay();
- }
- });
- } else {
- alertUserAboutError();
- }
- } catch (IOException | JSONException e) {
- Log.e(TAG, "Exception caught: " + e);
- }
- }
- });
- } else {
- //if network isn't available
- Toast.makeText(getActivity(), "Please connect to the network before continuing.", Toast.LENGTH_SHORT).show();
- }
-
- return view;
- }
-
- private void updateDisplay() {
- outsideTemp.setText(" " + mWeatherDetails.getTemp("celsius"));
- degreeSymbol.setText("\u00B0");
- roomTemp.setText(MainActivity.mainRoomTemp + "\u00B0");
- }
-
- private WeatherDetails getWeatherDetails(String jsonData) throws JSONException {
- WeatherDetails weatherDetails = new WeatherDetails();
-
- //make JSONObject for all JSON
- JSONObject forecast = new JSONObject(jsonData);
-
- //JSONObject for nested JSONObject inside 'forecast' for current weather details
- JSONObject currently = forecast.getJSONObject("currently");
-
- weatherDetails.setTemp(currently.getDouble("temperature"));
- weatherDetails.setIcon(currently.getString("icon"));
-
- return weatherDetails;
- }
-
- private void alertUserAboutError() {
- Toast.makeText(getActivity(), "There was an error retrieving weather data.", Toast.LENGTH_SHORT).show();
- }
-
- private boolean isNetworkAvailable() {
- ConnectivityManager manager = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- boolean isAvailable = false;
-
- //check if network is available and connected to web
- if (networkInfo != null && networkInfo.isConnected()) {
- isAvailable = true;
- }
-
- return isAvailable;
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/glance/WeatherDetails.java b/app/src/main/java/com/umarbhutta/xlightcompanion/glance/WeatherDetails.java
deleted file mode 100644
index 165a478..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/glance/WeatherDetails.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.umarbhutta.xlightcompanion.glance;
-
-/**
- * Created by Umar Bhutta.
- */
-public class WeatherDetails {
- private String mIcon;
- private double mTempF;
- private int mTempC;
-
- public String getIcon() {
- return mIcon;
- }
-
- public void setIcon(String mIcon) {
- this.mIcon = mIcon;
- }
-
- public int getTemp(String unit)
- {
- if (unit == "fahrenheit") {
- return (int) mTempF;
- } else {
- return mTempC;
- }
- }
-
- public void setTemp(double mTemp) {
- this.mTempF = mTemp;
-
- mTempC = (int) ((mTempF - 32.0) * (5.0/9.0));
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/main/MainActivity.java b/app/src/main/java/com/umarbhutta/xlightcompanion/main/MainActivity.java
deleted file mode 100644
index 98e255b..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/main/MainActivity.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.umarbhutta.xlightcompanion.main;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentTransaction;
-import android.support.design.widget.NavigationView;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.widget.DrawerLayout;
-import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.control.ControlFragment;
-import com.umarbhutta.xlightcompanion.glance.GlanceFragment;
-import com.umarbhutta.xlightcompanion.particle.ParticleBridge;
-import com.umarbhutta.xlightcompanion.scenario.ScenarioFragment;
-import com.umarbhutta.xlightcompanion.schedule.ScheduleFragment;
-
-public class MainActivity extends AppCompatActivity
- implements NavigationView.OnNavigationItemSelectedListener {
-
- public static int mainDevice_st = 0;
- public static int mainDevice_br = 50;
- public static int mainDevice_cct = 2700;
- public static int mainRoomTemp = 24;
-
- public static Handler handlerGlance = null;
- public static Handler handlerDeviceList = null;
- public static Handler handlerControl = null;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
-
- //login to Particle cloud
- ParticleBridge.authenticate(this);
-
- //setup drawer layout
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
- this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
- drawer.setDrawerListener(toggle);
- toggle.syncState();
-
- NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
- navigationView.setNavigationItemSelectedListener(this);
-
- displayView(R.id.nav_glance);
- navigationView.getMenu().getItem(0).setChecked(true);
- }
-
- public void displayView(int viewId) {
- Fragment fragment = null;
- String title = getString(R.string.app_name);
-
- switch (viewId) {
- case R.id.nav_glance:
- fragment = new GlanceFragment();
- title = "Glance";
- break;
- case R.id.nav_control:
- fragment = new ControlFragment();
- title = "Control";
- break;
- case R.id.nav_schedule:
- fragment = new ScheduleFragment();
- title = "Schedule";
- break;
- case R.id.nav_scenario:
- fragment = new ScenarioFragment();
- title = "Scenario";
- break;
- }
-
- if (fragment != null) {
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- ft.replace(R.id.placeholder, fragment);
- ft.commit();
- }
-
- // set the toolbar title
- if (getSupportActionBar() != null) {
- getSupportActionBar().setTitle(title);
- }
-
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- drawer.closeDrawer(GravityCompat.START);
- }
-
- @Override
- public void onBackPressed() {
- DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
- if (drawer.isDrawerOpen(GravityCompat.START)) {
- drawer.closeDrawer(GravityCompat.START);
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @SuppressWarnings("StatementWithEmptyBody")
- @Override
- public boolean onNavigationItemSelected(MenuItem item) {
- // Handle navigation view item clicks here.
- displayView(item.getItemId());
- return true;
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/particle/ParticleBridge.java b/app/src/main/java/com/umarbhutta/xlightcompanion/particle/ParticleBridge.java
deleted file mode 100644
index a943bbc..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/particle/ParticleBridge.java
+++ /dev/null
@@ -1,569 +0,0 @@
-package com.umarbhutta.xlightcompanion.particle;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import com.umarbhutta.xlightcompanion.main.MainActivity;
-import com.umarbhutta.xlightcompanion.scenario.ScenarioFragment;
-import com.umarbhutta.xlightcompanion.schedule.ScheduleFragment;
-import com.umarbhutta.xlightcompanion.particle.CloudAccount;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import io.particle.android.sdk.cloud.ParticleCloudException;
-import io.particle.android.sdk.cloud.ParticleCloudSDK;
-import io.particle.android.sdk.cloud.ParticleDevice;
-import io.particle.android.sdk.cloud.ParticleEvent;
-import io.particle.android.sdk.cloud.ParticleEventHandler;
-import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
-
-/**
- * Created by Umar Bhutta.
- */
-public class ParticleBridge {
- //misc
- private static final String TAG = ParticleBridge.class.getSimpleName();
-
- //Max num constants
- public static final int MAX_SCHEDULES = 6;
- public static final int MAX_DEVICES = 6;
-
- //Particle vars
- public static ParticleDevice currDevice;
- private static int resultCode;
- private static long subscriptionId = 0;
-
- //CLOUD FUNCTION CONSTS
- //cmd types
- public static final int VALUE_POWER = 1;
- public static final int VALUE_COLOR = 2;
- public static final int VALUE_BRIGHTNESS = 3;
- public static final int VALUE_SCENARIO = 4;
- public static final int VALUE_CCT = 5;
- public static final int VALUE_QUERY = 6;
- //device id
- public static final int DEFAULT_DEVICE_ID = 1;
- //ring values
- public static final int RING_ALL = 0;
- public static final int RING_1 = 1;
-
- public static final int RING_2 = 2;
- public static final int RING_3 = 3;
- //ring text
- public static final String DEFAULT_LAMP_TEXT = "LIVING ROOM";
- public static final String RINGALL_TEXT = "ALL RINGS";
- public static final String RING1_TEXT = "RING 1";
- public static final String RING2_TEXT = "RING 2";
- public static final String RING3_TEXT = "RING 3";
-
- //on/off values
- public static final int STATE_OFF = 0;
- public static final int STATE_ON = 1;
- //default alarm/filter id
- public static final int DEFAULT_ALARM_ID = 255;
- public static final int DEFAULT_FILTER_ID = 0;
-
- // Event names
- public static final String eventDeviceStatus = "xlc-status-device";
- public static final String eventSensorData = "xlc-data-sensor";
-
- //constants for testing lists
- public static final String[] deviceNames = {"Living Room", "Bedroom", "Basement Kitchen"};
- public static final String[] scheduleTimes = {"10:30 AM", "12:45 PM", "02:00 PM", "06:45 PM", "08:00 PM", "11:30 PM"};
- public static final String[] scheduleDays = {"Mo Tu We Th Fr", "Every day", "Mo We Th Sa Su", "Tomorrow", "We", "Mo Tu Fr Sa Su"};
- public static final String[] scenarioNames = {"Brunching", "Guests", "Naptime", "Dinner", "Sunset", "Bedtime"};
- public static final String[] scenarioDescriptions = {"A red color at 52% brightness", "A blue-green color at 100% brightness", "An amber color at 50% brightness", "Turn off", "A warm-white color at 100% brightness", "A green color at 52% brightness"};
- public static final String[] filterNames = {"Breathe", "Music Match", "Flash"};
-
-
- //Particle functions
- public static void authenticate(Context context) {
- ParticleDeviceSetupLibrary.init(context, MainActivity.class);
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- ParticleCloudSDK.getCloud().logIn(CloudAccount.EMAIL, CloudAccount.PASSWORD);
- currDevice = ParticleCloudSDK.getCloud().getDevice(CloudAccount.DEVICE_ID);
- SubscribeDeviceEvents();
-
- // Delay 2 seconds, then Query Main Device
- Handler myHandler = new Handler(Looper.getMainLooper());
- myHandler.postDelayed(new Runnable() {
- @Override
- public void run()
- {
- JSONCommandQueryDevice(1);
- }
- }, 2000);
-
- } catch (ParticleCloudException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
-
- public static int JSONCommandPower(final int nodeId, final boolean state) {
- new Thread() {
- @Override
- public void run() {
- int power = state ? 1 : 0;
-
- // Make the Particle call here
- String json = "{\"cmd\":" + VALUE_POWER + ",\"node_id\":" + nodeId + ",\"state\":" + power + "}";
- //String json = "{'cmd':" + VALUE_POWER + ",'node_id':" + nodeId + ",'state':" + power + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.e(TAG, "JSONCommandPower" + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONCommandBrightness(final int nodeId, final int value) {
- new Thread() {
- @Override
- public void run() {
- // Make the Particle call here
- String json = "{\"cmd\":" + VALUE_BRIGHTNESS + ",\"node_id\":" + nodeId + ",\"value\":" + value + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.e(TAG, "JSONCommandBrightness" + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONCommandCCT(final int nodeId, final int value) {
- new Thread() {
- @Override
- public void run() {
- // Make the Particle call here
- String json = "{\"cmd\":" + VALUE_CCT + ",\"node_id\":" + nodeId + ",\"value\":" + value + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.d(TAG, "JSONCommandCCT" + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONCommandColor(final int nodeId, final int ring, final boolean state, final int cw, final int ww, final int r, final int g, final int b) {
- new Thread() {
- @Override
- public void run() {
- // Make the Particle call here
- int power = state ? 1 : 0;
-
- String json = "{\"cmd\":" + VALUE_COLOR + ",\"node_id\":" + nodeId + ",\"ring\":" + ring + ",\"color\":[" + power + "," + cw + "," + ww + "," + r + "," + g + "," + b + "]}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.e(TAG, "JSONCommandColor " + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
-
- public static int JSONCommandScenario(final int nodeId, final int position) {
- new Thread() {
- @Override
- public void run() {
- //position corresponds to the spinner in Control. position of 1 corresponds to s1, 2 to s2. The 0th index in the spinner is the "None" item,
- //hence the parameter of position is good to go in this function as is - doesn't need to be incremented by 1 for the uid for scenario
-
- // Make the Particle call here
- String json = "{\"cmd\":" + VALUE_SCENARIO + ",\"node_id\":" + nodeId + ",\"SNT_id\":" + position + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.e(TAG, "JSONCommandScenario " + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONCommandQueryDevice(final int nodeId) {
- new Thread() {
- @Override
- public void run() {
- // Make the Particle call here
- String json = "{\"cmd\":" + VALUE_QUERY + ",\"node_id\":" + nodeId + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- try {
- Log.i(TAG, "JSONCommandQueryDevice" + message.get(0));
- resultCode = currDevice.callFunction("JSONCommand", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONConfigScenario(final int brightness, final int cw, final int ww, final int r, final int g, final int b, final String filter) {
- new Thread() {
- @Override
- public void run() {
- int scenarioId = ScenarioFragment.name.size();
- boolean x[] = {false, false, false};
-
- //construct first part of string input, and store it in arraylist (of size 1)
- String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"s" + scenarioId + "\",\"ring1\":" + " '}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- //send in first part of string
- try {
- Log.e(TAG, "JSONConfigScenario " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- x[0] = true;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
-
- if (x[0]) {
- //construct second part of string input, store in arraylist
- json = "{'x1': '[" + STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"ring2\":[" + STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "], '}";
- message.add(json);
- //send in second part of string
- try {
- Log.e(TAG, "JSONConfigScenario " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- x[1] = true;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
-
- if (x[1]) {
- //construct last part of string input, store in arraylist
- //json = "\"ring3\":[" + STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"brightness\":" + brightness + ",\"filter\":" + DEFAULT_FILTER_ID + "}";
- json = "\"ring3\":[" + STATE_ON + "," + cw + "," + ww + "," + r + "," + g + "," + b + "],\"brightness\":" + brightness + ",\"filter\":" + DEFAULT_FILTER_ID + "}";
- message.add(json);
- //send in last part of string
- try {
- Log.e(TAG, "JSONConfigScenario " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }
- }.start();
- return resultCode;
- }
-
- public static int JSONConfigAlarm(final int nodeId, final boolean isRepeat, final String weekdays, final int hour, final int minute, final String scenarioName) {
- final int[] doneSending = {0};
- new Thread() {
- @Override
- public void run() {
- boolean x[] = {false, false, false, false};
-
- //SCHEDULE
- int scheduleId = ScheduleFragment.name.size();
- int repeat = isRepeat ? 1 : 0;
-
- //construct first part of string input, and store it in arraylist (of size 1)
- String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"a" + scheduleId + "\",\"isRepeat\":" + "1" + ", '}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- //send in first part of string
- try {
- Log.e(TAG, "JSONConfigSchedule " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- x[0] = true;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
-
- if (x[0]) {
- //construct second part of string input, store in arraylist
- json = "\"weekdays\":" + "0" + ",\"hour\":" + hour + ",\"min\":" + minute + ",\"alarm_id\":" + DEFAULT_ALARM_ID + "}";
- message.add(json);
- //send in second part of string
- try {
- Log.e(TAG, "JSONConfigSchedule " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- x[1] = true;
- doneSending[0] = 5;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
-
-
-
- //RULE
- int rule_schedule_notif_Id = ScheduleFragment.name.size() - 1;
- int scenarioId = 1;
- for (int i = 0; i < ScenarioFragment.name.size(); i++) {
- if (scenarioName == ScenarioFragment.name.get(i)) {
- scenarioId = i;
- }
- }
-
- //construct first part of string input, and store it in arraylist (of size 1)
- String json2 = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"r" + rule_schedule_notif_Id + "\",\"node_id\":" + nodeId + ", '}";
- ArrayList message2 = new ArrayList<>();
- message2.add(json2);
- //send in first part of string
- try {
- Log.e(TAG, "JSONConfigRule " + message2.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message2);
- x[2] = true;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message2.clear();
-
- if (x[2]) {
- //construct second part of string input, store in arraylist
- json2 = "\"SCT_uid\":" + rule_schedule_notif_Id + ",\"SNT_uid\":" + scenarioId + ",\"notif_uid\":" + rule_schedule_notif_Id + "}";
- message2.add(json2);
- //send in second part of string
- try {
- Log.i(TAG, "JSONConfigRule " + message2.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message2);
- x[3] = true;
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message2.clear();
- }
- }
- }
- }.start();
- return resultCode;
- }
-
-// public static int JSONConfigRule(final int nodeId, final String scenarioName) {
-// new Thread() {
-// @Override
-// public void run() {
-// int rule_schedule_notif_Id = ScheduleFragment.name.size() + 1;
-// int scenarioId = 1;
-// for (int i = 0; i < ScenarioFragment.name.size(); i++) {
-// if (scenarioName == ScenarioFragment.name.get(i)) {
-// scenarioId = i + 1;
-// }
-// }
-// boolean x[] = {false, false};
-//
-// //construct first part of string input, and store it in arraylist (of size 1)
-// String json = "{'x0': '{\"op\":1,\"fl\":0,\"run\":0,\"uid\":\"r" + rule_schedule_notif_Id + "\",\"node_id\":" + nodeId + ", '}";
-// ArrayList message = new ArrayList<>();
-// message.add(json);
-// //send in first part of string
-// try {
-// Log.e(TAG, "JSONConfigRule" + message.get(0));
-// resultCode = currDevice.callFunction("JSONConfig", message);
-// x[0] = true;
-// } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
-// e.printStackTrace();
-// }
-// message.clear();
-//
-// if (x[0]) {
-// //construct second part of string input, store in arraylist
-// json = "\"SCT_uid\":\"a" + rule_schedule_notif_Id + "\",\"SNT_uid\":\"s" + scenarioId + "\",\"notif_uid\":\"n" + rule_schedule_notif_Id + "\"}";
-// message.add(json);
-// //send in second part of string
-// try {
-// Log.i(TAG, "JSONConfigRule" + message.get(0));
-// resultCode = currDevice.callFunction("JSONConfig", message);
-// x[1] = true;
-// } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
-// e.printStackTrace();
-// }
-// message.clear();
-// }
-// }
-// }.start();
-// return resultCode;
-// }
-
- public static int JSONGetDeviceStatus(final int nodeId) {
- new Thread() {
- @Override
- public void run() {
- //construct first part of string input, and store it in arraylist (of size 1)
- String json = "{\"op\":0,\"fl\":1,\"run\":0,\"uid\":\"h" + nodeId + "}";
- ArrayList message = new ArrayList<>();
- message.add(json);
- //send in first part of string
- try {
- Log.d(TAG, "JSONGetDeviceStatus " + message.get(0));
- resultCode = currDevice.callFunction("JSONConfig", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- message.clear();
- }
- }.start();
- return resultCode;
- }
-
- public static int FastCallPowerSwitch(final int nodeId, final boolean state) {
- new Thread() {
- @Override
- public void run() {
- // Make the Particle call here
- String strParam = String.format("%d:%d", nodeId, state ? 1 : 0);
- ArrayList message = new ArrayList<>();
- message.add(strParam);
- try {
- Log.d(TAG, "FastCallPowerSwitch: " + strParam);
- resultCode = currDevice.callFunction("PowerSwitch", message);
- } catch (ParticleCloudException | ParticleDevice.FunctionDoesNotExistException | IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- return resultCode;
- }
-
- // Particle events publishing & subscribing
- public static long SubscribeDeviceEvents() {
- new Thread() {
- @Override
- public void run() {
- try {
- subscriptionId = currDevice.subscribeToEvents(null, new ParticleEventHandler() {
- public void onEvent(String eventName, ParticleEvent event) {
- Log.i(TAG, "Received event: " + eventName + " with payload: " + event.dataPayload);
- try {
- JSONObject jObject = new JSONObject(event.dataPayload);
- //if (eventName.equalsIgnoreCase(eventDeviceStatus)) {
- if (jObject.has("nd")) {
- int nodeId = jObject.getInt("nd");
- if (nodeId == 1) {
-
- Message msgControlObj = null;
- Bundle bdlControl = null;
- if( MainActivity.handlerControl != null ) {
- msgControlObj = MainActivity.handlerControl.obtainMessage();
- bdlControl = new Bundle();
- }
-
- if (jObject.has("State")) {
- MainActivity.mainDevice_st = jObject.getInt("State");
- if( MainActivity.handlerDeviceList != null ) {
- Message msgObj = MainActivity.handlerDeviceList.obtainMessage();
- Bundle b = new Bundle();
- b.putInt("State", MainActivity.mainDevice_st);
- msgObj.setData(b);
- MainActivity.handlerDeviceList.sendMessage(msgObj);
- }
- if( MainActivity.handlerControl != null ) {
- bdlControl.putInt("State", MainActivity.mainDevice_st);
- }
- }
- if (jObject.has("BR")) {
- MainActivity.mainDevice_br = jObject.getInt("BR");
- if( MainActivity.handlerControl != null ) {
- bdlControl.putInt("BR", MainActivity.mainDevice_br);
- }
- }
- if (jObject.has("CCT")) {
- MainActivity.mainDevice_cct = jObject.getInt("CCT");
- if( MainActivity.handlerControl != null ) {
- bdlControl.putInt("CCT", MainActivity.mainDevice_cct);
- }
- }
-
- if( MainActivity.handlerControl != null && msgControlObj != null ) {
- msgControlObj.setData(bdlControl);
- MainActivity.handlerControl.sendMessage(msgControlObj);
- }
- }
- }
- //} else if (eventName.equalsIgnoreCase(eventSensorData)) {
- if (jObject.has("DHTt")) {
- MainActivity.mainRoomTemp = jObject.getInt("DHTt");
- if( MainActivity.handlerGlance != null ) {
- Message msgObj = MainActivity.handlerGlance.obtainMessage();
- Bundle b = new Bundle();
- b.putInt("DHTt", MainActivity.mainRoomTemp);
- msgObj.setData(b);
- MainActivity.handlerGlance.sendMessage(msgObj);
- }
- }
- //}
- } catch (final JSONException e) {
- Log.e(TAG, "Json parsing error: " + e.getMessage());
- }
- }
-
- public void onEventError(Exception e) {
- Log.e(TAG, "Event error: ", e);
- }
- });
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }.start();
- return subscriptionId;
- }
-
- public static void UnsubscribeDeviceEvents() {
- new Thread() {
- @Override
- public void run() {
- if( subscriptionId > 0 ) {
- try {
- currDevice.unsubscribeFromEvents(subscriptionId);
- } catch (ParticleCloudException e) {
- e.printStackTrace();
- }
- }
- }
- }.start();
- }
-}
diff --git a/app/src/main/java/com/umarbhutta/xlightcompanion/settings/SettingsFragment.java b/app/src/main/java/com/umarbhutta/xlightcompanion/settings/SettingsFragment.java
deleted file mode 100644
index 4d2827a..0000000
--- a/app/src/main/java/com/umarbhutta/xlightcompanion/settings/SettingsFragment.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.umarbhutta.xlightcompanion.settings;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.CompoundButton;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Spinner;
-import android.widget.Switch;
-import android.widget.TextView;
-import android.widget.ToggleButton;
-
-import com.umarbhutta.xlightcompanion.particle.ParticleBridge;
-import com.umarbhutta.xlightcompanion.R;
-import com.umarbhutta.xlightcompanion.scenario.ScenarioFragment;
-
-import java.util.ArrayList;
-
-import me.priyesh.chroma.ChromaDialog;
-import me.priyesh.chroma.ColorMode;
-import me.priyesh.chroma.ColorSelectListener;
-
-/**
- * Created by Umar Bhutta.
- */
-public class SettingsFragment extends PreferenceFragment {
-
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
-
- //get the sharedPreferences fields
- public String getStoragePreference() {
- return sharedPreferences.getString("tempUnits", "celsius");
- }
-
- //set the sharedPreferences
- public void setSharedPreferences(String unit) {
- sharedPreferences
- .edit()
- .putString("tempUnits", unit)
- .apply();
- }
-
-
-}
diff --git a/app/src/main/res/drawable-v21/ic_lightbulb_outline_black_24dp.xml b/app/src/main/res/drawable-v21/ic_lightbulb_outline_black_24dp.xml
index 2a8e9d7..c859e94 100644
--- a/app/src/main/res/drawable-v21/ic_lightbulb_outline_black_24dp.xml
+++ b/app/src/main/res/drawable-v21/ic_lightbulb_outline_black_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable-v21/ic_lightbulb_outline_green_24dp.xml b/app/src/main/res/drawable-v21/ic_lightbulb_outline_green_24dp.xml
new file mode 100644
index 0000000..035799e
--- /dev/null
+++ b/app/src/main/res/drawable-v21/ic_lightbulb_outline_green_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable-v21/ic_lightbulb_outline_grey_24dp.xml b/app/src/main/res/drawable-v21/ic_lightbulb_outline_grey_24dp.xml
index 49299f8..b7b01a1 100644
--- a/app/src/main/res/drawable-v21/ic_lightbulb_outline_grey_24dp.xml
+++ b/app/src/main/res/drawable-v21/ic_lightbulb_outline_grey_24dp.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
diff --git a/app/src/main/res/drawable-xxhdpi/bkg_press.png b/app/src/main/res/drawable-xxhdpi/bkg_press.png
new file mode 100644
index 0000000..a9a293c
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bkg_press.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/btn_delete.xml b/app/src/main/res/drawable-xxhdpi/btn_delete.xml
new file mode 100644
index 0000000..affb535
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/btn_delete.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/btn_mark.xml b/app/src/main/res/drawable-xxhdpi/btn_mark.xml
new file mode 100644
index 0000000..aa8ee09
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/btn_mark.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_delete_red_24dp.xml b/app/src/main/res/drawable/ic_delete_red_24dp.xml
new file mode 100644
index 0000000..a3f9e8a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_red_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete_red_48dp.xml b/app/src/main/res/drawable/ic_delete_red_48dp.xml
new file mode 100644
index 0000000..e7a3836
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_red_48dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_highlight_off_24dp.xml b/app/src/main/res/drawable/ic_highlight_off_24dp.xml
new file mode 100644
index 0000000..949c29f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_highlight_off_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_info_outline_24dp.xml b/app/src/main/res/drawable/ic_info_outline_24dp.xml
new file mode 100644
index 0000000..2cfec65
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info_outline_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml b/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml
new file mode 100644
index 0000000..a5025ae
--- /dev/null
+++ b/app/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_radio_button_checked_green_24dp.xml b/app/src/main/res/drawable/ic_radio_button_checked_green_24dp.xml
new file mode 100644
index 0000000..6dd3979
--- /dev/null
+++ b/app/src/main/res/drawable/ic_radio_button_checked_green_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_radio_button_checked_grey_24dp.xml b/app/src/main/res/drawable/ic_radio_button_checked_grey_24dp.xml
new file mode 100644
index 0000000..338583a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_radio_button_checked_grey_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_radio_button_checked_red_24dp.xml b/app/src/main/res/drawable/ic_radio_button_checked_red_24dp.xml
new file mode 100644
index 0000000..f85eb7b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_radio_button_checked_red_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings_blue_48dp.xml b/app/src/main/res/drawable/ic_settings_blue_48dp.xml
new file mode 100644
index 0000000..0ba980d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_blue_48dp.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/weather_icons_1.png b/app/src/main/res/drawable/weather_icons_1.png
new file mode 100644
index 0000000..076b9c9
Binary files /dev/null and b/app/src/main/res/drawable/weather_icons_1.png differ
diff --git a/app/src/main/res/layout/activity_add_new_device.xml b/app/src/main/res/layout/activity_add_new_device.xml
new file mode 100644
index 0000000..f12f050
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_new_device.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_device_type_spinner_item.xml b/app/src/main/res/layout/add_device_type_spinner_item.xml
new file mode 100644
index 0000000..03f6c87
--- /dev/null
+++ b/app/src/main/res/layout/add_device_type_spinner_item.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/devices_list_item.xml b/app/src/main/res/layout/devices_list_item.xml
index c718e3f..9bd53a9 100644
--- a/app/src/main/res/layout/devices_list_item.xml
+++ b/app/src/main/res/layout/devices_list_item.xml
@@ -1,11 +1,11 @@
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_control.xml b/app/src/main/res/layout/fragment_control.xml
index 5edea51..7b58d17 100644
--- a/app/src/main/res/layout/fragment_control.xml
+++ b/app/src/main/res/layout/fragment_control.xml
@@ -106,7 +106,7 @@
android:layout_weight="1"
android:fontFamily="sans-serif-light"
android:gravity="left|start"
- android:text="Scenario"
+ android:text="Scenario and Effect"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/textColorPrimary" />
diff --git a/app/src/main/res/layout/fragment_glance.xml b/app/src/main/res/layout/fragment_glance.xml
index 5f2f96f..7ba2975 100644
--- a/app/src/main/res/layout/fragment_glance.xml
+++ b/app/src/main/res/layout/fragment_glance.xml
@@ -27,6 +27,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -78,13 +151,37 @@
+
+
+
+
+
+
+
+
+ android:paddingLeft="20dp">
-
-
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index f18117d..0f5a277 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -5,21 +5,79 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
-
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_wifisetup.xml b/app/src/main/res/layout/fragment_wifisetup.xml
new file mode 100644
index 0000000..f47dc04
--- /dev/null
+++ b/app/src/main/res/layout/fragment_wifisetup.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/scenario_list_item.xml b/app/src/main/res/layout/scenario_list_item.xml
index bb0e357..7392cc1 100644
--- a/app/src/main/res/layout/scenario_list_item.xml
+++ b/app/src/main/res/layout/scenario_list_item.xml
@@ -51,7 +51,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/scenarioDelete"
- android:src="@drawable/ic_delete_black_24dp"
+ android:src="@drawable/ic_delete_red_24dp"
android:layout_weight="0.5"
android:layout_gravity="center_vertical" />
diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml
index af927d2..1dccc47 100644
--- a/app/src/main/res/menu/activity_main_drawer.xml
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -26,6 +26,10 @@
android:id="@+id/nav_settings"
android:icon="@drawable/ic_settings_black_24dp"
android:title="Settings" />
+
+ tools:context="ca.xlight.demoapp.schedule.SettingsFragment" >
- XLight
+ XLight DemoOpen navigation drawerClose navigation drawerSettings
+
+
+
+ New (Unknown)
+ Suzhou Demo
+ Reactor Demo
+ Gu An Demo
+ Redboard
+ Suzhou Sunny Mini
+
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+
+
+
+ Waterloo, ON
+ Suzhou, China
+ Gu An, China
+
+
+ 0
+ 1
+ 2
+
+
+
+ Auto
+ Cloud
+ Bluetooth
+
+
+ 0
+ 1
+ 2
+
+
+
+ Open
+ WEP
+ WPA
+ WPA2
+
+
+ 0
+ 1
+ 2
+ 3
+
+
+
+ Not Set
+ AES
+ TKIP
+ AES_TKIP
+
+
+ 0
+ 1
+ 2
+ 3
+
+
+
+ Sunny
+ Rainbow
+ Mirage
+ Switch
+
+
+ 32
+ 1
+ 96
+ 129
+
+
+ AddNewDevice
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 624ed13..bf023fd 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -1,4 +1,17 @@
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/umarbhutta/xlightcompanion/ExampleUnitTest.java b/app/src/test/java/ca/xlight/demoapp/ExampleUnitTest.java
similarity index 87%
rename from app/src/test/java/com/umarbhutta/xlightcompanion/ExampleUnitTest.java
rename to app/src/test/java/ca/xlight/demoapp/ExampleUnitTest.java
index 1b8b03b..45db8ba 100644
--- a/app/src/test/java/com/umarbhutta/xlightcompanion/ExampleUnitTest.java
+++ b/app/src/test/java/ca/xlight/demoapp/ExampleUnitTest.java
@@ -1,4 +1,4 @@
-package com.umarbhutta.xlightcompanion;
+package ca.xlight.demoapp;
import org.junit.Test;
diff --git a/bintray_upload_v1.gradle b/bintray_upload_v1.gradle
new file mode 100644
index 0000000..4d255ed
--- /dev/null
+++ b/bintray_upload_v1.gradle
@@ -0,0 +1,64 @@
+// lifted from https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
+// and copied into the repo for safety/stability
+apply plugin: 'com.jfrog.bintray'
+
+version = libraryVersion
+
+task sourcesJar(type: Jar) {
+ from android.sourceSets.main.java.srcDirs
+ classifier = 'sources'
+}
+
+task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+
+artifacts {
+ archives javadocJar
+ archives sourcesJar
+}
+
+// FIXME: this feels hackish, but it works for now, and it shouldn't
+// have any side effects* for anyone building the lib locally,
+// so #SHIPIT
+//
+// * My apologies if this turns out not to be true. Patches welcome!
+Properties authDataProps = new Properties()
+try {
+ authDataProps.load(project.rootProject.file('../bintray_user_auth_secrets.properties').newDataInputStream())
+} catch (Exception e) {
+ // do nothing; this is the default state for everyone who isn't publishing
+ // the lib.
+}
+
+bintray {
+ user = authDataProps.getProperty("bintray.user")
+ key = authDataProps.getProperty("bintray.apikey")
+
+ configurations = ['archives']
+ pkg {
+ userOrg = bintrayOrg
+ repo = bintrayRepo
+ name = bintrayName
+ desc = libraryDescription
+ websiteUrl = siteUrl
+ vcsUrl = gitUrl
+ licenses = allLicenses
+ publish = true
+ publicDownloadNumbers = false
+ version {
+// desc = libraryDescription
+ gpg {
+ sign = false // Determines whether to GPG sign the files. The default is false
+ // passphrase = properties.getProperty("bintray.gpg.password") // Optional. The passphrase for GPG signing'
+ }
+ }
+ }
+}
+
diff --git a/build.gradle b/build.gradle
index 53f4fad..215951e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,11 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.1'
+ classpath 'com.android.tools.build:gradle:2.3.2'
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.2'
+ classpath 'me.tatarka:gradle-retrolambda:3.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/cloudsdk/.gitignore b/cloudsdk/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/cloudsdk/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/cloudsdk/build.gradle b/cloudsdk/build.gradle
new file mode 100644
index 0000000..3d2e280
--- /dev/null
+++ b/cloudsdk/build.gradle
@@ -0,0 +1,92 @@
+apply plugin: 'com.android.library'
+apply plugin: 'me.tatarka.retrolambda'
+
+// This is the library version used when deploying the artifact
+version = '0.4.1'
+
+ext {
+ bintrayRepo = 'android'
+ bintrayName = 'cloud-sdk'
+ bintrayOrg = 'particle'
+
+ publishedGroupId = 'io.particle'
+ libraryName = 'Particle (formerly Spark) Android Cloud SDK library '
+ artifact = 'cloudsdk'
+
+ libraryDescription = 'Particle (formerly Spark) Android Cloud SDK library\n' +
+ 'The Particle Android Cloud SDK enables Android apps to interact with Particle-powered connected products via the Particle Cloud.\n' +
+ 'Library will allow you to easily manage active user sessions to Particle cloud, query for device info,\n' +
+ 'read and write data to/from Particle Core/Photon devices and (via exposed variables and functions)\n' +
+ 'publish and subscribe events to/from the cloud or to/from devices (coming soon).'
+
+ siteUrl = 'https://github.com/spark/spark-sdk-android'
+ gitUrl = 'https://github.com/spark/spark-sdk-android.git'
+
+ libraryVersion = project.version
+
+ developerId = 'idok'
+ developerName = 'Ido Kleinman'
+ developerEmail = 'ido@particle.io'
+
+ licenseName = 'The Apache Software License, Version 2.0'
+ licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ allLicenses = ["Apache-2.0"]
+}
+
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion '25.0.2'
+
+ dexOptions {
+ javaMaxHeapSize "2g"
+ }
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 25
+ versionCode 1
+ versionName project.version
+ consumerProguardFiles 'consumer-proguard-rules.pro'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+
+ compile 'com.google.code.findbugs:jsr305:3.0.1'
+ compile 'com.google.code.gson:gson:2.7'
+ compile 'com.squareup.okhttp:okhttp:2.7.5'
+ compile 'com.squareup.okio:okio:1.9.0'
+ compile 'com.squareup.retrofit:retrofit:1.9.0'
+ compile 'org.greenrobot:eventbus:3.0.0'
+
+ compile 'com.android.support:support-fragment:25.2.0'
+
+ retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:2.3.0'
+}
+
+apply from: '../pom_generator_v1.gradle'
+apply from: '../bintray_upload_v1.gradle'
+
+
+// disable insane, build-breaking doclint tool in Java 8
+if (JavaVersion.current().isJava8Compatible()) {
+ tasks.withType(Javadoc) {
+ //noinspection SpellCheckingInspection
+ options.addStringOption('Xdoclint:none', '-quiet')
+ }
+}
+
+
+apply plugin: 'com.getkeepsafe.dexcount'
diff --git a/cloudsdk/consumer-proguard-rules.pro b/cloudsdk/consumer-proguard-rules.pro
new file mode 100644
index 0000000..4fd6809
--- /dev/null
+++ b/cloudsdk/consumer-proguard-rules.pro
@@ -0,0 +1 @@
+-dontwarn okio.**
diff --git a/cloudsdk/proguard-rules.pro b/cloudsdk/proguard-rules.pro
new file mode 100644
index 0000000..9d244f6
--- /dev/null
+++ b/cloudsdk/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/jensck/Library/android-sdk-linux/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/cloudsdk/releasing.md b/cloudsdk/releasing.md
new file mode 100644
index 0000000..64c18c7
--- /dev/null
+++ b/cloudsdk/releasing.md
@@ -0,0 +1,17 @@
+# Making official releases
+
+These are the steps for releasing an updated version of the Particle SDK.
+For example, if you were releasing version `2.4.2`, you'd do the following:
+
+1. Make sure the CHANGELOG is current
+2. Pull from origin to ensure you have the latest upstream changes
+3. Update the `version` field in `cloudsdk/build.gradle` to `'2.4.2'`
+4. Build a release and publish it to JCenter. From the `cloudsdk` dir,
+do: `../gradlew clean build install bintrayUpload`
+5. Submit a PR to the docs site updating the version code in `android.md` to `2.4.2`
+6. Update the example app to pull the new version from JCenter, clean its build, and
+then build & run the example app as a final smoke test.
+7. Commit and push the previous two changes
+8. Tag the release: `git tag v2.4.2` (note the "v" at the beginning)
+9. Push the tag: `git push origin v2.4.2` (again, note the "v")
+10. (if applicable) announce the update via the appropriate channels
diff --git a/cloudsdk/src/androidTest/java/io/particle/android/sdk/cloud/ApplicationTest.java b/cloudsdk/src/androidTest/java/io/particle/android/sdk/cloud/ApplicationTest.java
new file mode 100644
index 0000000..344ea05
--- /dev/null
+++ b/cloudsdk/src/androidTest/java/io/particle/android/sdk/cloud/ApplicationTest.java
@@ -0,0 +1,13 @@
+package io.particle.android.sdk.cloud;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/cloudsdk/src/main/AndroidManifest.xml b/cloudsdk/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a704bac
--- /dev/null
+++ b/cloudsdk/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiDefs.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiDefs.java
new file mode 100644
index 0000000..77a9457
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiDefs.java
@@ -0,0 +1,145 @@
+package io.particle.android.sdk.cloud;
+
+import java.util.List;
+
+import io.particle.android.sdk.cloud.Responses.CallFunctionResponse;
+import io.particle.android.sdk.cloud.Responses.ClaimCodeResponse;
+import io.particle.android.sdk.cloud.Responses.Models;
+import io.particle.android.sdk.cloud.Responses.ReadDoubleVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadIntVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadObjectVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadStringVariableResponse;
+import io.particle.android.sdk.cloud.Responses.SimpleResponse;
+import io.particle.android.sdk.cloud.models.SignUpInfo;
+import retrofit.client.Response;
+import retrofit.http.Body;
+import retrofit.http.DELETE;
+import retrofit.http.Field;
+import retrofit.http.FormUrlEncoded;
+import retrofit.http.GET;
+import retrofit.http.Multipart;
+import retrofit.http.POST;
+import retrofit.http.PUT;
+import retrofit.http.Part;
+import retrofit.http.Path;
+import retrofit.mime.TypedOutput;
+
+
+/**
+ * Particle cloud REST APIs, modelled for the Retrofit library
+ */
+public class ApiDefs {
+
+ // FIXME: turn some of these common strings into constants?
+
+ /**
+ * The main Particle cloud API
+ */
+ public interface CloudApi {
+
+ @GET("/v1/devices")
+ List getDevices();
+
+ @GET("/v1/devices/{deviceID}")
+ Models.CompleteDevice getDevice(@Path("deviceID") String deviceID);
+
+ // FIXME: put a real response type on this?
+ @FormUrlEncoded
+ @PUT("/v1/devices/{deviceID}")
+ Response nameDevice(@Path("deviceID") String deviceID,
+ @Field("name") String name);
+
+ @FormUrlEncoded
+ @PUT("/v1/devices/{deviceID}")
+ Response flashKnownApp(@Path("deviceID") String deviceID,
+ @Field("app") String appName);
+
+ @Multipart
+ @PUT("/v1/devices/{deviceID}")
+ Response flashFile(@Path("deviceID") String deviceID,
+ @Part("file") TypedOutput file);
+
+ @POST("/v1/devices/{deviceID}/{function}")
+ CallFunctionResponse callFunction(@Path("deviceID") String deviceID,
+ @Path("function") String function,
+ @Body FunctionArgs args);
+
+ @GET("/v1/devices/{deviceID}/{variable}")
+ ReadObjectVariableResponse getVariable(@Path("deviceID") String deviceID,
+ @Path("variable") String variable);
+
+ @GET("/v1/devices/{deviceID}/{variable}")
+ ReadIntVariableResponse getIntVariable(@Path("deviceID") String deviceID,
+ @Path("variable") String variable);
+
+ @GET("/v1/devices/{deviceID}/{variable}")
+ ReadStringVariableResponse getStringVariable(@Path("deviceID") String deviceID,
+ @Path("variable") String variable);
+
+ @GET("/v1/devices/{deviceID}/{variable}")
+ ReadDoubleVariableResponse getDoubleVariable(@Path("deviceID") String deviceID,
+ @Path("variable") String variable);
+
+ @FormUrlEncoded
+ @POST("/v1/devices/events")
+ SimpleResponse publishEvent(@Field("name") String eventName,
+ @Field("data") String eventData,
+ @Field("private") boolean isPrivate,
+ @Field("ttl") int timeToLive);
+
+ /**
+ * Newer versions of OkHttp require a body for POSTs, but just pass in
+ * a blank string for the body and all is well.
+ */
+ @FormUrlEncoded
+ @POST("/v1/device_claims")
+ ClaimCodeResponse generateClaimCode(@Field("blank") String blankBody);
+
+ @FormUrlEncoded
+ @POST("/v1/orgs/{orgSlug}/products/{productSlug}/device_claims")
+ ClaimCodeResponse generateClaimCodeForOrg(@Field("blank") String blankBody,
+ @Path("orgSlug") String orgSlug,
+ @Path("productSlug") String productSlug);
+
+ @FormUrlEncoded
+ @POST("/v1/devices")
+ SimpleResponse claimDevice(@Field("id") String deviceID);
+
+ @DELETE("/v1/devices/{deviceID}")
+ SimpleResponse unclaimDevice(@Path("deviceID") String deviceID);
+
+ }
+
+ /**
+ * APIs dealing with identity and authorization
+ *
+ * These are separated out from the main API, since they aren't
+ * authenticated like the main API, and as such need different
+ * headers.
+ */
+ public interface IdentityApi {
+
+ @POST("/v1/users")
+ Response signUp(@Body SignUpInfo signUpInfo);
+
+
+ // NOTE: the `LogInResponse` used here as a return type is intentional. It looks
+ // a little odd, but that's how this endpoint works.
+ @POST("/v1/orgs/{orgSlug}/customers")
+ Responses.LogInResponse signUpAndLogInWithCustomer(@Body SignUpInfo signUpInfo,
+ @Path("orgSlug") String orgSlug);
+
+ @FormUrlEncoded
+ @POST("/oauth/token")
+ Responses.LogInResponse logIn(@Field("grant_type") String grantType,
+ @Field("username") String username,
+ @Field("password") String password);
+
+ @FormUrlEncoded
+ @POST("/v1/password/reset")
+// @POST("/v1/orgs/{orgName}/customers/reset_password")
+ Response requestPasswordReset(@Field("email") String email);//,
+// @Path("orgName") String orgName);
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiFactory.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiFactory.java
new file mode 100644
index 0000000..163342b
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ApiFactory.java
@@ -0,0 +1,148 @@
+package io.particle.android.sdk.cloud;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.StringRes;
+import android.util.Base64;
+
+import com.google.gson.Gson;
+import com.squareup.okhttp.OkHttpClient;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import retrofit.RestAdapter;
+import retrofit.RestAdapter.LogLevel;
+import retrofit.client.OkClient;
+import retrofit.converter.GsonConverter;
+
+/**
+ * Constructs ParticleCloud instances
+ */
+@ParametersAreNonnullByDefault
+public class ApiFactory {
+
+ // both values are in seconds
+ private static final int REGULAR_TIMEOUT = 35;
+ // FIXME: find a less cheesy solution to the "rapid timeout" problem
+ private static final int PER_DEVICE_FAST_TIMEOUT = 5;
+
+
+ // FIXME: this feels kind of lame... but maybe it's OK in practice. Need to think more about it.
+ public interface TokenGetterDelegate {
+
+ String getTokenValue();
+
+ }
+
+
+ public interface OauthBasicAuthCredentialsProvider {
+
+ String getClientId();
+
+ String getClientSecret();
+ }
+
+
+ private final Context ctx;
+ private final TokenGetterDelegate tokenDelegate;
+ private final OkHttpClient normalTimeoutClient;
+ private final OkHttpClient fastTimeoutClient;
+ private final OauthBasicAuthCredentialsProvider basicAuthCredentialsProvider;
+ private final Gson gson;
+
+ ApiFactory(Context ctx, TokenGetterDelegate tokenGetterDelegate,
+ OauthBasicAuthCredentialsProvider basicAuthProvider) {
+ this.ctx = ctx.getApplicationContext();
+ this.tokenDelegate = tokenGetterDelegate;
+ this.basicAuthCredentialsProvider = basicAuthProvider;
+ this.gson = new Gson();
+
+ normalTimeoutClient = buildClientWithTimeout(REGULAR_TIMEOUT);
+ fastTimeoutClient = buildClientWithTimeout(PER_DEVICE_FAST_TIMEOUT);
+ }
+
+ private static OkHttpClient buildClientWithTimeout(int timeoutInSeconds) {
+ OkHttpClient client = new OkHttpClient();
+ client.setConnectTimeout(timeoutInSeconds, TimeUnit.SECONDS);
+ client.setReadTimeout(timeoutInSeconds, TimeUnit.SECONDS);
+ client.setWriteTimeout(timeoutInSeconds, TimeUnit.SECONDS);
+ return client;
+ }
+
+ ApiDefs.CloudApi buildNewCloudApi() {
+ RestAdapter restAdapter = buildCommonRestAdapterBuilder(gson, normalTimeoutClient)
+ .setRequestInterceptor(request -> request.addHeader("Authorization", "Bearer " +
+ tokenDelegate.getTokenValue()))
+ .build();
+ return restAdapter.create(ApiDefs.CloudApi.class);
+ }
+
+ // FIXME: fix this ugliness
+ ApiDefs.CloudApi buildNewFastTimeoutCloudApi() {
+ RestAdapter restAdapter = buildCommonRestAdapterBuilder(gson, fastTimeoutClient)
+ .setRequestInterceptor(request -> request.addHeader("Authorization", "Bearer " +
+ tokenDelegate.getTokenValue()))
+ .build();
+ return restAdapter.create(ApiDefs.CloudApi.class);
+ }
+
+ ApiDefs.IdentityApi buildNewIdentityApi() {
+ final String basicAuthValue = getBasicAuthValue();
+
+ RestAdapter restAdapter = buildCommonRestAdapterBuilder(gson, normalTimeoutClient)
+ .setRequestInterceptor(request -> request.addHeader("Authorization", basicAuthValue))
+ .build();
+ return restAdapter.create(ApiDefs.IdentityApi.class);
+ }
+
+ Uri getApiUri() {
+ return Uri.parse(ctx.getString(R.string.api_base_uri));
+ }
+
+ Gson getGsonInstance() {
+ return gson;
+ }
+
+ private String getBasicAuthValue() {
+ String authString = String.format("%s:%s",
+ basicAuthCredentialsProvider.getClientId(),
+ basicAuthCredentialsProvider.getClientSecret());
+ return "Basic " + Base64.encodeToString(authString.getBytes(), Base64.NO_WRAP);
+ }
+
+ private RestAdapter.Builder buildCommonRestAdapterBuilder(Gson gson, OkHttpClient client) {
+ return new RestAdapter.Builder()
+ .setClient(new OkClient(client))
+ .setConverter(new GsonConverter(gson))
+ .setEndpoint(getApiUri().toString())
+ .setLogLevel(LogLevel.valueOf(ctx.getString(R.string.http_log_level)));
+ }
+
+
+ public static class ResourceValueBasicAuthCredentialsProvider
+ implements OauthBasicAuthCredentialsProvider {
+
+ private final String clientId;
+ private final String clientSecret;
+
+ public ResourceValueBasicAuthCredentialsProvider(
+ Context ctx, @StringRes int clientIdResId, @StringRes int clientSecretResId) {
+ this.clientId = ctx.getString(clientIdResId);
+ this.clientSecret = ctx.getString(clientSecretResId);
+ }
+
+
+ @Override
+ public String getClientId() {
+ return clientId;
+ }
+
+ @Override
+ public String getClientSecret() {
+ return clientSecret;
+ }
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/BroadcastContract.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/BroadcastContract.java
new file mode 100644
index 0000000..126036d
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/BroadcastContract.java
@@ -0,0 +1,9 @@
+package io.particle.android.sdk.cloud;
+
+
+public interface BroadcastContract {
+
+ String BROADCAST_DEVICES_UPDATED = "BROADCAST_DEVICES_UPDATED";
+ String BROADCAST_SYSTEM_EVENT = "BROADCAST_SYSTEM_EVENT";
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/DeviceState.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/DeviceState.java
new file mode 100644
index 0000000..c3117fc
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/DeviceState.java
@@ -0,0 +1,284 @@
+package io.particle.android.sdk.cloud;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.ParticleDevice.VariableType;
+import io.particle.android.sdk.utils.Parcelables;
+
+
+// FIXME: I'm about ready to give up on trying to make this actually immutable. Bah.
+// Instead, make an IDeviceState or something, which is an immutable interface, and then have a
+// MutableDeviceState class which will also have setters, and only expose the mutable concrete
+// class to whatever class ends up doing the device state management; *everything* else only ever
+// gets to see IDeviceState objects. (This might interfere with using Parcelable though.)
+// FIXME: is device "state" really the right naming here?
+@ParametersAreNonnullByDefault
+class DeviceState implements Parcelable {
+
+ final String deviceId;
+ @Nullable final Integer platformId;
+ @Nullable final Integer productId;
+ @Nullable final String ipAddress;
+ @Nullable final String lastAppName;
+ @Nullable final String status;
+ @Nullable final String name;
+ @Nullable final Boolean isConnected;
+ @Nullable final Boolean cellular;
+ @Nullable final String imei;
+ @Nullable final String currentBuild;
+ @Nullable final String defaultBuild;
+ final Set functions;
+ final Map variables;
+ @Nullable final String version;
+ @Nullable final ParticleDevice.ParticleDeviceType deviceType;
+ @Nullable final Boolean requiresUpdate;
+ @Nullable final Date lastHeard;
+
+ DeviceState(DeviceStateBuilder deviceStateBuilder) {
+ this.deviceId = deviceStateBuilder.deviceId;
+ this.name = deviceStateBuilder.name;
+ this.isConnected = deviceStateBuilder.isConnected;
+ this.cellular = deviceStateBuilder.cellular;
+ this.imei = deviceStateBuilder.imei;
+ this.currentBuild = deviceStateBuilder.currentBuild;
+ this.defaultBuild = deviceStateBuilder.defaultBuild;
+ this.functions = deviceStateBuilder.functions;
+ this.variables = deviceStateBuilder.variables;
+ this.version = deviceStateBuilder.version == null ? "" : deviceStateBuilder.version;
+ this.deviceType = deviceStateBuilder.deviceType;
+ this.platformId = deviceStateBuilder.platformId;
+ this.productId = deviceStateBuilder.productId;
+ this.ipAddress = deviceStateBuilder.ipAddress;
+ this.lastAppName = deviceStateBuilder.lastAppName;
+ this.status = deviceStateBuilder.status;
+ this.requiresUpdate = deviceStateBuilder.requiresUpdate;
+ this.lastHeard = deviceStateBuilder.lastHeard;
+ }
+
+ //region ImmutabilityPhun
+ // The following static builder methods are awkward and a little absurd, but they still seem
+ // better than the alternatives. If we have to add another couple mutable fields though, it
+ // might be time to reconsider this...
+ static DeviceState withNewName(DeviceState other, String newName) {
+ return new DeviceStateBuilder(other.deviceId, other.functions, other.variables)
+ .name(newName)
+ .cellular(other.cellular)
+ .connected(other.isConnected)
+ .version(other.version)
+ .deviceType(other.deviceType)
+ .platformId(other.platformId)
+ .productId(other.productId)
+ .imei(other.imei)
+ .currentBuild(other.currentBuild)
+ .defaultBuild(other.defaultBuild)
+ .ipAddress(other.ipAddress)
+ .lastAppName(other.lastAppName)
+ .status(other.status)
+ .requiresUpdate(other.requiresUpdate)
+ .lastHeard(other.lastHeard)
+ .build();
+ }
+
+
+ static DeviceState withNewConnectedState(DeviceState other, boolean newConnectedState) {
+ return new DeviceStateBuilder(other.deviceId, other.functions, other.variables)
+ .name(other.name)
+ .cellular(other.cellular)
+ .connected(newConnectedState)
+ .version(other.version)
+ .deviceType(other.deviceType)
+ .platformId(other.platformId)
+ .productId(other.productId)
+ .imei(other.imei)
+ .currentBuild(other.currentBuild)
+ .defaultBuild(other.defaultBuild)
+ .ipAddress(other.ipAddress)
+ .lastAppName(other.lastAppName)
+ .status(other.status)
+ .requiresUpdate(other.requiresUpdate)
+ .lastHeard(other.lastHeard)
+ .build();
+ }
+ //endregion
+
+ //region Parcelable
+ private DeviceState(Parcel in) {
+ deviceId = in.readString();
+ name = (String) in.readValue(String.class.getClassLoader());
+ isConnected = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ functions = new HashSet<>(Parcelables.readStringList(in));
+ variables = Parcelables.readSerializableMap(in);
+ version = (String) in.readValue(String.class.getClassLoader());
+ deviceType = ParticleDevice.ParticleDeviceType.valueOf((String) in.readValue(String.class.getClassLoader()));
+ platformId = (Integer) in.readValue(Integer.class.getClassLoader());
+ productId = (Integer) in.readValue(Integer.class.getClassLoader());
+ cellular = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ imei = (String) in.readValue(String.class.getClassLoader());
+ currentBuild = (String) in.readValue(String.class.getClassLoader());
+ defaultBuild = (String) in.readValue(String.class.getClassLoader());
+ ipAddress = (String) in.readValue(String.class.getClassLoader());
+ lastAppName = (String) in.readValue(String.class.getClassLoader());
+ status = (String) in.readValue(String.class.getClassLoader());
+ requiresUpdate = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ lastHeard = new Date((Long) in.readValue(Long.class.getClassLoader()));
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(deviceId);
+ dest.writeValue(name);
+ dest.writeValue(isConnected);
+ dest.writeStringList(new ArrayList<>(functions));
+ Parcelables.writeSerializableMap(dest, variables);
+ dest.writeValue(version);
+ dest.writeValue(deviceType != null ? deviceType.name() : null);
+ dest.writeValue(platformId);
+ dest.writeValue(productId);
+ dest.writeValue(cellular);
+ dest.writeValue(imei);
+ dest.writeValue(currentBuild);
+ dest.writeValue(defaultBuild);
+ dest.writeValue(ipAddress);
+ dest.writeValue(lastAppName);
+ dest.writeValue(status);
+ dest.writeValue(requiresUpdate);
+ dest.writeValue(lastHeard != null ? lastHeard.getTime() : 0);
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public DeviceState createFromParcel(Parcel in) {
+ return new DeviceState(in);
+ }
+
+ @Override
+ public DeviceState[] newArray(int size) {
+ return new DeviceState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ //endregion
+
+ public static class DeviceStateBuilder {
+ private final String deviceId;
+ @Nullable private Integer platformId;
+ @Nullable private Integer productId;
+ @Nullable private String ipAddress;
+ @Nullable private String lastAppName;
+ @Nullable private String status;
+ @Nullable private String name;
+ @Nullable private Boolean isConnected;
+ @Nullable private Boolean cellular;
+ @Nullable private String imei;
+ @Nullable private String currentBuild;
+ @Nullable private String defaultBuild;
+ private final Set functions;
+ private final Map variables;
+ @Nullable String version;
+ @Nullable ParticleDevice.ParticleDeviceType deviceType;
+ @Nullable Boolean requiresUpdate;
+ @Nullable Date lastHeard;
+
+
+ DeviceStateBuilder(String deviceId, Set functions, Map variables) {
+ this.deviceId = deviceId;
+ this.functions = functions;
+ this.variables = variables;
+ this.version = version == null ? "" : version;
+ }
+
+ public DeviceStateBuilder platformId(@Nullable Integer platformId) {
+ this.platformId = platformId;
+ return this;
+ }
+
+ public DeviceStateBuilder productId(@Nullable Integer productId) {
+ this.productId = productId;
+ return this;
+ }
+
+ public DeviceStateBuilder ipAddress(@Nullable String ipAddress) {
+ this.ipAddress = ipAddress;
+ return this;
+ }
+
+ public DeviceStateBuilder lastAppName(@Nullable String lastAppName) {
+ this.lastAppName = lastAppName;
+ return this;
+ }
+
+ public DeviceStateBuilder status(@Nullable String status) {
+ this.status = status;
+ return this;
+ }
+
+ public DeviceStateBuilder name(@Nullable String name) {
+ this.name = name;
+ return this;
+ }
+
+ public DeviceStateBuilder connected(@Nullable Boolean connected) {
+ isConnected = connected;
+ return this;
+ }
+
+ public DeviceStateBuilder cellular(@Nullable Boolean cellular) {
+ this.cellular = cellular;
+ return this;
+ }
+
+ public DeviceStateBuilder imei(@Nullable String imei) {
+ this.imei = imei;
+ return this;
+ }
+
+ public DeviceStateBuilder currentBuild(@Nullable String currentBuild) {
+ this.currentBuild = currentBuild;
+ return this;
+ }
+
+ public DeviceStateBuilder defaultBuild(@Nullable String defaultBuild) {
+ this.defaultBuild = defaultBuild;
+ return this;
+ }
+
+ public DeviceStateBuilder version(@Nullable String version) {
+ this.version = version;
+ return this;
+ }
+
+ public DeviceStateBuilder deviceType(@Nullable ParticleDevice.ParticleDeviceType deviceType) {
+ this.deviceType = deviceType;
+ return this;
+ }
+
+ public DeviceStateBuilder requiresUpdate(@Nullable Boolean requiresUpdate) {
+ this.requiresUpdate = requiresUpdate;
+ return this;
+ }
+
+ public DeviceStateBuilder lastHeard(@Nullable Date lastHeard) {
+ this.lastHeard = lastHeard;
+ return this;
+ }
+
+ public DeviceState build() {
+ return new DeviceState(this);
+ }
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/EventsDelegate.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/EventsDelegate.java
new file mode 100644
index 0000000..28541d1
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/EventsDelegate.java
@@ -0,0 +1,255 @@
+package io.particle.android.sdk.cloud;
+
+
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.support.v4.util.LongSparseArray;
+
+import com.google.gson.Gson;
+
+import org.kaazing.net.sse.SseEventReader;
+import org.kaazing.net.sse.SseEventSource;
+import org.kaazing.net.sse.SseEventSourceFactory;
+import org.kaazing.net.sse.SseEventType;
+import org.kaazing.net.sse.impl.AuthenticatedEventSourceFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.ApiDefs.CloudApi;
+import io.particle.android.sdk.utils.TLog;
+import retrofit.RetrofitError;
+
+import static io.particle.android.sdk.utils.Py.truthy;
+
+
+// See javadoc on ParticleCloud for the intended behavior of these methods
+@ParametersAreNonnullByDefault
+class EventsDelegate {
+
+ private static final TLog log = TLog.get(EventsDelegate.class);
+
+ private final CloudApi cloudApi;
+ private final EventApiUris uris;
+ private final Gson gson;
+ private final ExecutorService executor;
+ private final SseEventSourceFactory eventSourceFactory;
+
+ private final AtomicLong subscriptionIdGenerator = new AtomicLong(1);
+ private final LongSparseArray eventReaders = new LongSparseArray<>();
+
+ EventsDelegate(CloudApi cloudApi, Uri baseApiUri, Gson gson, ExecutorService executor,
+ ParticleCloud cloud) {
+ this.cloudApi = cloudApi;
+ this.gson = gson;
+ this.executor = executor;
+ this.eventSourceFactory = new AuthenticatedEventSourceFactory(cloud);
+ this.uris = new EventApiUris(baseApiUri);
+ }
+
+ @WorkerThread
+ void publishEvent(String eventName, String event,
+ @ParticleEventVisibility int eventVisibility, int timeToLive)
+ throws ParticleCloudException {
+
+ boolean isPrivate = eventVisibility != ParticleEventVisibility.PUBLIC;
+ try {
+ cloudApi.publishEvent(eventName, event, isPrivate, timeToLive);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ @WorkerThread
+ long subscribeToAllEvents(@Nullable String eventNamePrefix,
+ ParticleEventHandler handler) throws IOException {
+ return subscribeToEventWithUri(uris.buildAllEventsUri(eventNamePrefix), handler);
+ }
+
+ @WorkerThread
+ long subscribeToMyDevicesEvents(@Nullable String eventNamePrefix,
+ ParticleEventHandler handler) throws IOException {
+ return subscribeToEventWithUri(uris.buildMyDevicesEventUri(eventNamePrefix), handler);
+ }
+
+ @WorkerThread
+ long subscribeToDeviceEvents(@Nullable String eventNamePrefix, String deviceID,
+ ParticleEventHandler eventHandler) throws IOException {
+ return subscribeToEventWithUri(
+ uris.buildSingleDeviceEventUri(eventNamePrefix, deviceID),
+ eventHandler);
+ }
+
+ @WorkerThread
+ void unsubscribeFromEventWithID(long eventListenerID) throws ParticleCloudException {
+ synchronized (eventReaders) {
+ EventReader reader = eventReaders.get(eventListenerID);
+ if (reader == null) {
+ log.w("No event listener subscription found for ID '" + eventListenerID + "'!");
+ return;
+ }
+ eventReaders.remove(eventListenerID);
+ try {
+ reader.stopListening();
+ } catch (IOException e) {
+ // handling the exception here instead of putting it in the method signature
+ // is inconsistent, but SDK consumers aren't going to care about receiving
+ // this exception, so just swallow it here.
+ log.w("Error while trying to stop event listener", e);
+ }
+ }
+ }
+
+ @WorkerThread
+ void unsubscribeFromEventWithHandler(SimpleParticleEventHandler handler) throws ParticleCloudException {
+ synchronized (eventReaders) {
+ for (int i = 0; i < eventReaders.size(); i++) {
+ EventReader reader = eventReaders.valueAt(i);
+
+ if (reader.handler == handler) {
+ eventReaders.remove(i);
+ try {
+ reader.stopListening();
+ } catch (IOException e) {
+ // handling the exception here instead of putting it in the method signature
+ // is inconsistent, but SDK consumers aren't going to care about receiving
+ // this exception, so just swallow it here.
+ log.w("Error while trying to stop event listener", e);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ private long subscribeToEventWithUri(Uri uri, ParticleEventHandler handler) throws IOException {
+ synchronized (eventReaders) {
+
+ long subscriptionId = subscriptionIdGenerator.getAndIncrement();
+ EventReader reader = new EventReader(handler, executor, gson, uri, eventSourceFactory);
+ eventReaders.put(subscriptionId, reader);
+
+ log.d("Created event subscription with ID " + subscriptionId + " for URI " + uri);
+
+ reader.startListening();
+
+ return subscriptionId;
+ }
+ }
+
+
+ private static class EventReader {
+
+ final ParticleEventHandler handler;
+ final SseEventSource sseEventSource;
+ final ExecutorService executor;
+ final Gson gson;
+
+ volatile Future> future;
+
+ private EventReader(ParticleEventHandler handler, ExecutorService executor, Gson gson,
+ Uri uri, SseEventSourceFactory factory) {
+ this.handler = handler;
+ this.executor = executor;
+ this.gson = gson;
+ try {
+ sseEventSource = factory.createEventSource(URI.create(uri.toString()));
+ } catch (URISyntaxException e) {
+ // I don't like throwing exceptions in constructors, but this URI shouldn't be in
+ // the wrong format...
+ throw new RuntimeException(e);
+ }
+ }
+
+ void startListening() throws IOException {
+ sseEventSource.connect();
+ final SseEventReader sseEventReader = sseEventSource.getEventReader();
+ future = executor.submit(() -> startHandlingEvents(sseEventReader));
+ }
+
+ void stopListening() throws IOException {
+ future.cancel(false);
+ sseEventSource.close();
+ }
+
+
+ private void startHandlingEvents(SseEventReader sseEventReader) {
+ SseEventType type;
+ try {
+ type = sseEventReader.next();
+ while (type != SseEventType.EOS) {
+
+ if (type != null && type.equals(SseEventType.DATA)) {
+ CharSequence data = sseEventReader.getData();
+ String asStr = data.toString();
+
+ ParticleEvent event = gson.fromJson(asStr, ParticleEvent.class);
+
+ try {
+ handler.onEvent(sseEventReader.getName(), event);
+ } catch (Exception ex) {
+ handler.onEventError(ex);
+ }
+ } else {
+ log.w("type null or not data: " + type);
+ }
+ type = sseEventReader.next();
+ }
+ } catch (IOException e) {
+ handler.onEventError(e);
+ }
+ }
+ }
+
+
+ // FIXME: Start sharing some of the strings with the constants that need to be defined in ApiDefs
+ private static class EventApiUris {
+
+ private final String EVENTS = "events";
+
+ private final Uri allEventsUri;
+ private final Uri devicesBaseUri;
+ private final Uri myDevicesEventsUri;
+
+ EventApiUris(Uri baseUri) {
+ allEventsUri = baseUri.buildUpon().path("/v1/" + EVENTS).build();
+ devicesBaseUri = baseUri.buildUpon().path("/v1/devices").build();
+ myDevicesEventsUri = devicesBaseUri.buildUpon().appendPath(EVENTS).build();
+ }
+
+ Uri buildAllEventsUri(@Nullable String eventNamePrefix) {
+ if (truthy(eventNamePrefix)) {
+ return allEventsUri.buildUpon().appendPath(eventNamePrefix).build();
+ } else {
+ return allEventsUri;
+ }
+ }
+
+ Uri buildMyDevicesEventUri(@Nullable String eventNamePrefix) {
+ if (truthy(eventNamePrefix)) {
+ return myDevicesEventsUri.buildUpon().appendPath(eventNamePrefix).build();
+ } else {
+ return myDevicesEventsUri;
+ }
+ }
+
+ Uri buildSingleDeviceEventUri(@Nullable String eventNamePrefix, String deviceId) {
+ Builder builder = devicesBaseUri.buildUpon()
+ .appendPath(deviceId)
+ .appendPath(EVENTS);
+ if (truthy(eventNamePrefix)) {
+ builder.appendPath(eventNamePrefix);
+ }
+ return builder.build();
+ }
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/FunctionArgs.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/FunctionArgs.java
new file mode 100644
index 0000000..8a27d66
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/FunctionArgs.java
@@ -0,0 +1,13 @@
+package io.particle.android.sdk.cloud;
+
+import com.google.gson.annotations.SerializedName;
+
+public class FunctionArgs {
+
+ @SerializedName("params")
+ public final String params;
+
+ public FunctionArgs(String params) {
+ this.params = params;
+ }
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParallelDeviceFetcher.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParallelDeviceFetcher.java
new file mode 100644
index 0000000..307f4ab
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParallelDeviceFetcher.java
@@ -0,0 +1,122 @@
+package io.particle.android.sdk.cloud;
+
+import android.support.annotation.CheckResult;
+import android.support.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.ApiDefs.CloudApi;
+import io.particle.android.sdk.cloud.Responses.Models;
+import io.particle.android.sdk.cloud.Responses.Models.CompleteDevice;
+import io.particle.android.sdk.cloud.Responses.Models.SimpleDevice;
+
+import static io.particle.android.sdk.utils.Py.list;
+
+/**
+ * Does parallel fetching of {@link Models.CompleteDevice}
+ *
+ * FIXME: review this solution
+ */
+@ParametersAreNonnullByDefault
+class ParallelDeviceFetcher {
+
+ static class DeviceFetchResult {
+
+ final String deviceId;
+ /**
+ * Will be null if device could not be fetched.
+ */
+ @Nullable
+ final CompleteDevice fetchedDevice;
+
+ DeviceFetchResult(String deviceId, @Nullable CompleteDevice fetchedDevice) {
+ this.deviceId = deviceId;
+ this.fetchedDevice = fetchedDevice;
+ }
+ }
+
+
+ // FIXME: insert slower API here
+ static ParallelDeviceFetcher newFetcherUsingExecutor(ExecutorService executor) {
+ return new ParallelDeviceFetcher(executor);
+ }
+
+
+ private final ExecutorService executor;
+
+ private ParallelDeviceFetcher(ExecutorService executor) {
+ this.executor = executor;
+ }
+
+ // FIXME: ugh, so lame. Figure out the smarter way to do per-device fetch timeouts
+ // without having to resort to two Retrofit API instances. look into jsr166 ForkJoinPool
+ // or similar (since we can't use the Java 7 one until API 21...)
+ /**
+ * Fetch the devices in parallel. Ordering of results not guaranteed to be preserved or
+ * respected in any way.
+ */
+ @CheckResult
+ Collection fetchDevicesInParallel(Collection simpleDevices,
+ final CloudApi cloudApi,
+ int perDeviceTimeoutInSeconds) {
+ // Assemble the list of Callables
+ List> callables = list();
+ for (final SimpleDevice device : simpleDevices) {
+ callables.add(new Callable() {
+ @Override
+ public DeviceFetchResult call() throws Exception {
+ return getDevice(cloudApi, device.id);
+ }
+ });
+ }
+
+
+ // Submit callables, receive list of Futures. invokeAll() will block until they finish.
+ List> futures = list();
+ try {
+ long timeout = perDeviceTimeoutInSeconds * simpleDevices.size();
+ futures = executor.invokeAll(callables, timeout, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // FIXME: think about what to do in this implausible(?) scenario, or how to avoid it.
+ e.printStackTrace();
+ }
+
+
+ // turn the results into something usable.
+ List results = list();
+ for (Future future : futures) {
+ try {
+ DeviceFetchResult result = future.get();
+ if (result != null) {
+ results.add(result);
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ // FIXME: see above; think more about what to do in this scenario, or how to avoid it.
+ e.printStackTrace();
+ }
+ }
+
+ return results;
+ }
+
+
+ private DeviceFetchResult getDevice(CloudApi cloudApi, String deviceID) {
+ CompleteDevice device = null;
+ try {
+ device = cloudApi.getDevice(deviceID);
+ } catch (Exception e) {
+ // doesn't matter why it fails, just don't abort the whole operation because of it
+ }
+ return new DeviceFetchResult(deviceID, device);
+ }
+
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleAccessToken.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleAccessToken.java
new file mode 100644
index 0000000..323eaab
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleAccessToken.java
@@ -0,0 +1,195 @@
+package io.particle.android.sdk.cloud;
+
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.Date;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.persistance.SensitiveDataStorage;
+import io.particle.android.sdk.utils.EZ;
+import io.particle.android.sdk.utils.Py;
+import io.particle.android.sdk.utils.TLog;
+
+
+@ParametersAreNonnullByDefault
+public class ParticleAccessToken {
+
+
+ public interface ParticleAccessTokenDelegate {
+
+ void accessTokenExpiredAt(ParticleAccessToken token, Date expirationDate);
+
+ }
+
+
+ public static synchronized ParticleAccessToken fromNewSession(Responses.LogInResponse logInResponse) {
+ if (logInResponse == null
+ || !Py.truthy(logInResponse.accessToken)
+ || !"bearer".equalsIgnoreCase(logInResponse.tokenType)) {
+ throw new IllegalArgumentException("Invalid LogInResponse: " + logInResponse);
+ }
+
+ long expirationMillis = logInResponse.expiresInSeconds * 1000;
+ Date expirationDate = new Date(System.currentTimeMillis() + expirationMillis);
+ return fromTokenData(expirationDate, logInResponse.accessToken);
+ }
+
+
+ @Nullable
+ public static synchronized ParticleAccessToken fromSavedSession() {
+ SensitiveDataStorage sensitiveDataStorage = SDKGlobals.getSensitiveDataStorage();
+ String accessToken = sensitiveDataStorage.getToken();
+ Date expirationDate = sensitiveDataStorage.getTokenExpirationDate();
+
+ // are either of the fields "falsey" or has the expr date passed?
+ if (!Py.truthy(accessToken) || !Py.truthy(expirationDate) || expirationDate.before(new Date())) {
+ return null;
+ }
+
+ ParticleAccessToken token = new ParticleAccessToken(accessToken, expirationDate,
+ new Handler(Looper.getMainLooper()));
+ token.scheduleExpiration();
+ return token;
+ }
+
+
+ public static synchronized ParticleAccessToken fromTokenData(Date expirationDate, String accessToken) {
+ SensitiveDataStorage sensitiveDataStorage = SDKGlobals.getSensitiveDataStorage();
+ sensitiveDataStorage.saveToken(accessToken);
+ sensitiveDataStorage.saveTokenExpirationDate(expirationDate);
+
+ ParticleAccessToken token = new ParticleAccessToken(accessToken, expirationDate,
+ new Handler(Looper.getMainLooper()));
+ token.scheduleExpiration();
+ return token;
+ }
+
+
+ /**
+ * Remove access token session data from keychain
+ */
+ public static synchronized void removeSession() {
+ SensitiveDataStorage sensitiveDataStorage = SDKGlobals.getSensitiveDataStorage();
+ sensitiveDataStorage.resetToken();
+ sensitiveDataStorage.resetTokenExpirationDate();
+ }
+
+ private static final TLog log = TLog.get(ParticleAccessToken.class);
+
+ // how many seconds before expiry date will a token be considered expired
+ // (0 = expire on expiry date, 24*60*60 = expire a day before)
+ // FIXME: should this be considered configurable?
+ private static final int ACCESS_TOKEN_EXPIRY_MARGIN = 0;
+
+ private final Handler handler;
+
+ private String accessToken;
+ private Date expiryDate;
+
+ private volatile Runnable expirationRunnable;
+ private volatile ParticleAccessTokenDelegate delegate;
+
+ private ParticleAccessToken(String accessToken, Date expiryDate, Handler handler) {
+ this.accessToken = accessToken;
+ this.expiryDate = expiryDate;
+ this.handler = handler;
+ }
+
+ /**
+ * Access token string to be used when calling cloud API
+ *
+ * @return null if token is expired.
+ */
+ public String getAccessToken() {
+ if (expiryDate.getTime() + ACCESS_TOKEN_EXPIRY_MARGIN < System.currentTimeMillis()) {
+ return null;
+ }
+ return accessToken;
+ }
+
+ /**
+ * Delegate to receive didExpireAt method call whenever a token is detected as expired
+ */
+ public ParticleAccessTokenDelegate getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Set the delegate described in #getDelegate()
+ */
+ public void setDelegate(ParticleAccessTokenDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ private void onExpiration() {
+ log.d("Entering onExpiration()");
+ this.expirationRunnable = null;
+
+ if (this.delegate == null) {
+ log.w("Token expiration delegate is null");
+ this.accessToken = null;
+ return;
+ }
+
+ // ensure that we don't call accessTokenExpiredAt() on the main thread, since
+ // the delegate (in the default impl) will make a call to try logging back
+ // in, but making network calls on the main thread is doubleplus ungood.
+ // (It'll throw an exception if you even try this, as well it should!)
+ EZ.runAsync(new Runnable() {
+ @Override
+ public void run() {
+ delegate.accessTokenExpiredAt(ParticleAccessToken.this, expiryDate);
+ }
+ });
+ }
+
+ private void scheduleExpiration() {
+ long delay = expiryDate.getTime() - System.currentTimeMillis();
+ log.d("Scheduling token expiration for " + expiryDate + " (" + delay + "ms.");
+ handler.postDelayed(new ExpirationHandler(this), delay);
+ }
+
+ // visible because I don't want to completely trust the finalizer to call this...
+ void cancelExpiration() {
+ if (expirationRunnable != null) {
+ handler.removeCallbacks(expirationRunnable);
+ expirationRunnable = null;
+ }
+ }
+
+ // FIXME: finalizers are a _last resort_. Look for something better.
+ @Override
+ protected void finalize() throws Throwable {
+ cancelExpiration();
+ super.finalize();
+ }
+
+
+ private static class ExpirationHandler implements Runnable {
+
+ final WeakReference tokenRef;
+
+ private ExpirationHandler(ParticleAccessToken token) {
+ this.tokenRef = new WeakReference<>(token);
+ }
+
+
+ @Override
+ public void run() {
+ log.d("Running token expiration handler...");
+ ParticleAccessToken token = tokenRef.get();
+ if (token == null) {
+ log.d("...but the token was null, doing nothing.");
+ return;
+ }
+ token.onExpiration();
+ tokenRef.clear();
+ }
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloud.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloud.java
new file mode 100644
index 0000000..05fc17c
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloud.java
@@ -0,0 +1,781 @@
+package io.particle.android.sdk.cloud;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v4.util.ArrayMap;
+
+import com.google.gson.Gson;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.ApiDefs.CloudApi;
+import io.particle.android.sdk.cloud.ParallelDeviceFetcher.DeviceFetchResult;
+import io.particle.android.sdk.cloud.ParticleDevice.ParticleDeviceType;
+import io.particle.android.sdk.cloud.ParticleDevice.VariableType;
+import io.particle.android.sdk.cloud.Responses.Models;
+import io.particle.android.sdk.cloud.Responses.Models.CompleteDevice;
+import io.particle.android.sdk.cloud.Responses.Models.SimpleDevice;
+import io.particle.android.sdk.cloud.models.DeviceStateChange;
+import io.particle.android.sdk.cloud.models.SignUpInfo;
+import io.particle.android.sdk.persistance.AppDataStorage;
+import io.particle.android.sdk.utils.Funcy;
+import io.particle.android.sdk.utils.Funcy.Func;
+import io.particle.android.sdk.utils.Py.PySet;
+import io.particle.android.sdk.utils.TLog;
+import retrofit.RetrofitError;
+
+import static io.particle.android.sdk.utils.Py.all;
+import static io.particle.android.sdk.utils.Py.list;
+import static io.particle.android.sdk.utils.Py.set;
+import static io.particle.android.sdk.utils.Py.truthy;
+
+
+// FIXME: move device state management out to another class
+// FIXME: move some of the type conversion junk out of this into another class, too
+
+// this is an SDK; it's expected it won't reference all its own methods
+@SuppressWarnings({"UnusedDeclaration"})
+@ParametersAreNonnullByDefault
+public class ParticleCloud {
+
+ private static final TLog log = TLog.get(ParticleCloud.class);
+
+ /**
+ * Singleton instance of ParticleCloud class
+ *
+ * @return ParticleCloud
+ * @deprecated use {@link ParticleCloudSDK#getCloud()} instead. This interface will be removed
+ * some time before the 1.0 release.
+ */
+ @Deprecated
+ public synchronized static ParticleCloud get(Context context) {
+ log.w("ParticleCloud.get() is deprecated and will be removed before the 1.0 release. " +
+ "Use ParticleCloudSDK.getCloud() instead!");
+ if (!ParticleCloudSDK.isInitialized()) {
+ ParticleCloudSDK.init(context);
+ }
+ return ParticleCloudSDK.getCloud();
+ }
+
+ private final ApiDefs.CloudApi mainApi;
+ private final ApiDefs.IdentityApi identityApi;
+ // FIXME: document why this exists (and try to make it not exist...)
+ private final ApiDefs.CloudApi deviceFastTimeoutApi;
+ private final AppDataStorage appDataStorage;
+ private final TokenDelegate tokenDelegate = new TokenDelegate();
+ private final LocalBroadcastManager broadcastManager;
+ private final EventsDelegate eventsDelegate;
+ private final ParallelDeviceFetcher parallelDeviceFetcher;
+
+ private final Map devices = new ArrayMap<>();
+
+ // We should be able to mark these both @Nullable, but Android Studio has been incorrectly
+ // inferring that these could be null in code blocks which _directly follow a null check_.
+ // Try again later after a few more releases, I guess...
+// @Nullable
+ private volatile ParticleAccessToken token;
+ // @Nullable
+ private volatile ParticleUser user;
+
+ ParticleCloud(Uri schemeAndHostname,
+ ApiDefs.CloudApi mainApi,
+ ApiDefs.IdentityApi identityApi,
+ ApiDefs.CloudApi perDeviceFastTimeoutApi,
+ AppDataStorage appDataStorage, LocalBroadcastManager broadcastManager,
+ Gson gson, ExecutorService executor) {
+ this.mainApi = mainApi;
+ this.identityApi = identityApi;
+ this.deviceFastTimeoutApi = perDeviceFastTimeoutApi;
+ this.appDataStorage = appDataStorage;
+ this.broadcastManager = broadcastManager;
+ this.user = ParticleUser.fromSavedSession();
+ this.token = ParticleAccessToken.fromSavedSession();
+ if (this.token != null) {
+ this.token.setDelegate(new TokenDelegate());
+ }
+ this.eventsDelegate = new EventsDelegate(mainApi, schemeAndHostname, gson, executor, this);
+ this.parallelDeviceFetcher = ParallelDeviceFetcher.newFetcherUsingExecutor(executor);
+ }
+
+ //region general public API
+
+ /**
+ * Current session access token string. Can be null.
+ */
+ @Nullable
+ public String getAccessToken() {
+ return (this.token == null) ? null : this.token.getAccessToken();
+ }
+
+ public void setAccessToken(String tokenString, Date expirationDate) {
+ ParticleAccessToken.removeSession();
+ this.token = ParticleAccessToken.fromTokenData(expirationDate, tokenString);
+ this.token.setDelegate(tokenDelegate);
+ }
+
+ /**
+ * Currently logged in user name, or null if no session exists
+ */
+ @Nullable
+ public String getLoggedInUsername() {
+ return all(this.token, this.user) ? this.user.getUser() : null;
+ }
+
+ public boolean isLoggedIn() {
+ return getLoggedInUsername() != null;
+ }
+
+ /**
+ * Login with existing account credentials to Particle cloud
+ *
+ * @param user User name, must be a valid email address
+ * @param password Password
+ */
+ @WorkerThread
+ public void logIn(String user, String password) throws ParticleCloudException {
+ try {
+ Responses.LogInResponse response = identityApi.logIn("password", user, password);
+ onLogIn(response, user, password);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Sign up with new account credentials to Particle cloud
+ *
+ * @param user Required user name, must be a valid email address
+ * @param password Required password
+ */
+ @WorkerThread
+ public void signUpWithUser(String user, String password) throws ParticleCloudException {
+ try {
+ identityApi.signUp(new SignUpInfo(user, password));
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Sign up with new account credentials to Particle cloud
+ *
+ * @param signUpInfo Required sign up information, must contain a valid email address and password
+ */
+ @WorkerThread
+ public void signUpWithUser(SignUpInfo signUpInfo) throws ParticleCloudException {
+ try {
+ identityApi.signUp(signUpInfo);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Create new customer account on the Particle cloud and log in
+ *
+ * @param email Required user name, must be a valid email address
+ * @param password Required password
+ * @param orgSlug Organization slug to use
+ */
+ @WorkerThread
+ public void signUpAndLogInWithCustomer(String email, String password, String orgSlug)
+ throws ParticleCloudException {
+ try {
+ signUpAndLogInWithCustomer(new SignUpInfo(email, password), orgSlug);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Create new customer account on the Particle cloud and log in
+ *
+ * @param signUpInfo Required sign up information, must contain a valid email address and password
+ * @param orgSlug Organization slug to use
+ */
+ @WorkerThread
+ public void signUpAndLogInWithCustomer(SignUpInfo signUpInfo, String orgSlug)
+ throws ParticleCloudException {
+ if (!all(signUpInfo.getUsername(), signUpInfo.getPassword(), orgSlug)) {
+ throw new IllegalArgumentException(
+ "Email, password, and organization must all be specified");
+ }
+
+ signUpInfo.setGrantType("client_credentials");
+ try {
+ Responses.LogInResponse response = identityApi.signUpAndLogInWithCustomer(signUpInfo, orgSlug);
+ onLogIn(response, signUpInfo.getUsername(), signUpInfo.getPassword());
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Logout user, remove session data
+ */
+ public void logOut() {
+ if (token != null) {
+ token.cancelExpiration();
+ }
+ ParticleUser.removeSession();
+ ParticleAccessToken.removeSession();
+ token = null;
+ user = null;
+ }
+
+ /**
+ * Get an array of instances of all user's claimed devices
+ */
+ @WorkerThread
+ public List getDevices() throws ParticleCloudException {
+ List simpleDevices;
+ try {
+ simpleDevices = mainApi.getDevices();
+
+ appDataStorage.saveUserHasClaimedDevices(truthy(simpleDevices));
+
+ List result = list();
+
+ for (Models.SimpleDevice simpleDevice : simpleDevices) {
+ ParticleDevice device;
+ if (simpleDevice.isConnected) {
+ device = getDevice(simpleDevice.id, false);
+ } else {
+ device = getOfflineDevice(simpleDevice);
+ }
+ result.add(device);
+ }
+
+ pruneDeviceMap(simpleDevices);
+
+ return result;
+
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ // FIXME: devise a less temporary way to expose this method
+ // FIXME: stop the duplication that's happening here
+ // FIXME: ...think harder about this whole thing. This is unique in that it's the only
+ // operation that could _partially_ succeed.
+ @WorkerThread
+ List getDevicesParallel(boolean useShortTimeout)
+ throws PartialDeviceListResultException, ParticleCloudException {
+ List simpleDevices;
+ try {
+ simpleDevices = mainApi.getDevices();
+ appDataStorage.saveUserHasClaimedDevices(truthy(simpleDevices));
+
+
+ // divide up into online and offline
+ List offlineDevices = list();
+ List onlineDevices = list();
+
+ for (Models.SimpleDevice simpleDevice : simpleDevices) {
+ List targetList = (simpleDevice.isConnected)
+ ? onlineDevices
+ : offlineDevices;
+ targetList.add(simpleDevice);
+ }
+
+
+ List result = list();
+
+ // handle the offline devices
+ for (SimpleDevice offlineDevice : offlineDevices) {
+ result.add(getOfflineDevice(offlineDevice));
+ }
+
+
+ // handle the online devices
+ CloudApi apiToUse = (useShortTimeout)
+ ? deviceFastTimeoutApi
+ : mainApi;
+ // FIXME: don't hardcode this here
+ int timeoutInSecs = useShortTimeout ? 5 : 35;
+ Collection results = parallelDeviceFetcher.fetchDevicesInParallel(
+ onlineDevices, apiToUse, timeoutInSecs);
+
+ // FIXME: make this logic more elegant
+ boolean shouldThrowIncompleteException = false;
+ for (DeviceFetchResult fetchResult : results) {
+ // fetchResult shouldn't be null, but...
+ // FIXME: eliminate this ambiguity ^^^, it's either possible that it's null, or it isn't.
+ if (fetchResult == null || fetchResult.fetchedDevice == null) {
+ shouldThrowIncompleteException = true;
+ } else {
+ result.add(getDevice(fetchResult.fetchedDevice, false));
+ }
+ }
+
+ pruneDeviceMap(simpleDevices);
+
+ if (shouldThrowIncompleteException) {
+ throw new PartialDeviceListResultException(result);
+ }
+
+ return result;
+
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Get a specific device instance by its deviceID
+ *
+ * @param deviceID required deviceID
+ * @return the device instance on success
+ */
+ @WorkerThread
+ public ParticleDevice getDevice(String deviceID) throws ParticleCloudException {
+ return getDevice(deviceID, true);
+ }
+
+ /**
+ * Claim the specified device to the currently logged in user (without claim code mechanism)
+ *
+ * @param deviceID the deviceID
+ */
+ @WorkerThread
+ public void claimDevice(String deviceID) throws ParticleCloudException {
+ try {
+ mainApi.claimDevice(deviceID);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ /**
+ * Get a short-lived claiming token for transmitting to soon-to-be-claimed device in
+ * soft AP setup process
+ *
+ * @return a claim code string set on success (48 random bytes, base64 encoded
+ * to 64 ASCII characters)
+ */
+ @WorkerThread
+ public Responses.ClaimCodeResponse generateClaimCode() throws ParticleCloudException {
+ try {
+ // Offer empty string to appease newer OkHttp versions which require a POST body,
+ // even if it's empty or (as far as the endpoint cares) nonsense
+ return mainApi.generateClaimCode("okhttp_appeasement");
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ @WorkerThread
+ public Responses.ClaimCodeResponse generateClaimCodeForOrg(String orgSlug, String productSlug)
+ throws ParticleCloudException {
+ try {
+ // Offer empty string to appease newer OkHttp versions which require a POST body,
+ // even if it's empty or (as far as the endpoint cares) nonsense
+ return mainApi.generateClaimCodeForOrg("okhttp_appeasement", orgSlug, productSlug);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+
+ // TODO: check if any javadoc has been added for this method in the iOS SDK
+ @WorkerThread
+ public void requestPasswordReset(String email) throws ParticleCloudException {
+ try {
+ identityApi.requestPasswordReset(email);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+ }
+ //endregion
+
+
+ //region Events pub/sub methods
+
+ /**
+ * Subscribe to events from one specific device. If the API user has the device claimed, then
+ * she will receive all events, public and private, published by that device. If the API user
+ * does not own the device she will only receive public events.
+ *
+ * @param eventName The name for the event
+ * @param event A JSON-formatted string to use as the event payload
+ * @param eventVisibility An IntDef "enum" determining the visibility of the event
+ * @param timeToLive TTL, or Time To Live: a piece of event metadata representing the
+ * number of seconds that the event data is still considered relevant.
+ * After the TTL has passed, event listeners should consider the
+ * information stale or out of date.
+ * e.g.: an outdoor temperature reading might have a TTL of somewhere
+ * between 600 (10 minutes) and 1800 (30 minutes). The geolocation of a
+ * large piece of farm equipment which remains stationary most of the
+ * time but may be moved to a different field once in a while might
+ * have a TTL of 86400 (24 hours).
+ */
+ @WorkerThread
+ public void publishEvent(String eventName, String event,
+ @ParticleEventVisibility int eventVisibility, int timeToLive)
+ throws ParticleCloudException {
+ eventsDelegate.publishEvent(eventName, event, eventVisibility, timeToLive);
+ }
+
+ /**
+ * Subscribe to the firehose of public events, plus all private events published by
+ * the devices the API user owns.
+ *
+ * @param eventNamePrefix A string to filter on for events. If null, all events will be matched.
+ * @param handler The ParticleEventHandler to receive the events
+ * @return a unique subscription ID for the eventListener that's been registered. This ID is
+ * used to unsubscribe this event listener later.
+ */
+ @WorkerThread
+ public long subscribeToAllEvents(@Nullable String eventNamePrefix, ParticleEventHandler handler)
+ throws IOException {
+ return eventsDelegate.subscribeToAllEvents(eventNamePrefix, handler);
+ }
+
+ /**
+ * Subscribe to all events, public and private, published by devices owned by the logged-in account.
+ *
+ * see {@link #subscribeToAllEvents(String, ParticleEventHandler)} for info on the
+ * arguments and return value.
+ */
+ @WorkerThread
+ public long subscribeToMyDevicesEvents(@Nullable String eventNamePrefix,
+ ParticleEventHandler handler)
+ throws IOException {
+ return eventsDelegate.subscribeToMyDevicesEvents(eventNamePrefix, handler);
+ }
+
+ /**
+ * Subscribe to events from a specific device.
+ *
+ * If the API user has claimed the device, then she will receive all events, public and private,
+ * published by this device. If the API user does not own the device, she will only
+ * receive public events.
+ *
+ * @param deviceID the device to listen to events from
+ *
+ * see {@link #subscribeToAllEvents(String, ParticleEventHandler)} for info on the
+ * arguments and return value.
+ */
+ @WorkerThread
+ public long subscribeToDeviceEvents(@Nullable String eventNamePrefix, String deviceID,
+ ParticleEventHandler eventHandler)
+ throws IOException {
+ return eventsDelegate.subscribeToDeviceEvents(eventNamePrefix, deviceID, eventHandler);
+ }
+
+ /**
+ * Unsubscribe event listener from events.
+ *
+ * @param eventListenerID The ID of the event listener you want to unsubscribe from events
+ */
+ @WorkerThread
+ public void unsubscribeFromEventWithID(long eventListenerID) throws ParticleCloudException {
+ eventsDelegate.unsubscribeFromEventWithID(eventListenerID);
+ }
+
+ /**
+ * Unsubscribe event listener from events.
+ *
+ * @param handler Particle event listener you want to unsubscribe from events
+ */
+ @WorkerThread
+ void unsubscribeFromEventWithHandler(SimpleParticleEventHandler handler) throws ParticleCloudException {
+ eventsDelegate.unsubscribeFromEventWithHandler(handler);
+ }
+ //endregion
+
+
+ //region package-only API
+ @WorkerThread
+ void unclaimDevice(String deviceId) {
+ mainApi.unclaimDevice(deviceId);
+ synchronized (devices) {
+ devices.remove(deviceId);
+ }
+ sendUpdateBroadcast();
+ }
+
+ @WorkerThread
+ void rename(String deviceId, String newName) throws ParticleCloudException {
+ ParticleDevice particleDevice;
+ synchronized (devices) {
+ particleDevice = devices.get(deviceId);
+ }
+ DeviceState originalDeviceState = particleDevice.deviceState;
+
+ DeviceState stateWithNewName = DeviceState.withNewName(originalDeviceState, newName);
+ updateDeviceState(stateWithNewName, true);
+ try {
+ mainApi.nameDevice(originalDeviceState.deviceId, newName);
+ } catch (RetrofitError e) {
+ // oops, change the name back.
+ updateDeviceState(originalDeviceState, true);
+ throw new ParticleCloudException(e);
+ }
+ }
+
+ @Deprecated
+ @WorkerThread
+ void changeDeviceName(String deviceId, String newName) throws ParticleCloudException {
+ rename(deviceId, newName);
+ }
+
+ @WorkerThread
+ // Called when a cloud API call receives a result in which the "coreInfo.connected" is false
+ void onDeviceNotConnected(DeviceState deviceState) {
+ DeviceState newState = DeviceState.withNewConnectedState(deviceState, false);
+ updateDeviceState(newState, true);
+ }
+
+ // FIXME: exposing this is weak, figure out something better
+ void notifyDeviceChanged() {
+ sendUpdateBroadcast();
+ }
+
+ void sendSystemEventBroadcast(DeviceStateChange stateChange) {
+ Intent intent = new Intent(BroadcastContract.BROADCAST_SYSTEM_EVENT);
+ intent.putExtra("event", stateChange);
+ broadcastManager.sendBroadcast(intent);
+ }
+
+ // this is accessible at the package level for access from ParticleDevice's Parcelable impl
+ ParticleDevice getDeviceFromState(DeviceState deviceState) {
+ synchronized (devices) {
+ if (devices.containsKey(deviceState.deviceId)) {
+ return devices.get(deviceState.deviceId);
+ } else {
+ ParticleDevice device = new ParticleDevice(mainApi, this, deviceState);
+ devices.put(deviceState.deviceId, device);
+ return device;
+ }
+ }
+ }
+ //endregion
+
+
+ //region private API
+ @WorkerThread
+ private ParticleDevice getDevice(String deviceID, boolean sendUpdate)
+ throws ParticleCloudException {
+ CompleteDevice deviceCloudModel;
+ try {
+ deviceCloudModel = mainApi.getDevice(deviceID);
+ } catch (RetrofitError error) {
+ throw new ParticleCloudException(error);
+ }
+
+ return getDevice(deviceCloudModel, sendUpdate);
+ }
+
+ private ParticleDevice getDevice(CompleteDevice deviceModel, boolean sendUpdate) {
+ DeviceState newDeviceState = fromCompleteDevice(deviceModel);
+ ParticleDevice device = getDeviceFromState(newDeviceState);
+ updateDeviceState(newDeviceState, sendUpdate);
+ return device;
+ }
+
+ private ParticleDevice getOfflineDevice(Models.SimpleDevice offlineDevice) {
+ DeviceState newDeviceState = fromSimpleDeviceModel(offlineDevice);
+ ParticleDevice device = getDeviceFromState(newDeviceState);
+ updateDeviceState(newDeviceState, false);
+ return device;
+ }
+
+ private void updateDeviceState(DeviceState newState, boolean sendUpdateBroadcast) {
+ ParticleDevice device = getDeviceFromState(newState);
+ device.deviceState = newState;
+ if (sendUpdateBroadcast) {
+ sendUpdateBroadcast();
+ }
+ }
+
+ private void sendUpdateBroadcast() {
+ broadcastManager.sendBroadcast(new Intent(BroadcastContract.BROADCAST_DEVICES_UPDATED));
+ }
+
+ private void onLogIn(Responses.LogInResponse response, String user, String password) {
+ ParticleAccessToken.removeSession();
+ this.token = ParticleAccessToken.fromNewSession(response);
+ this.token.setDelegate(tokenDelegate);
+ this.user = ParticleUser.fromNewCredentials(user, password);
+ }
+
+ private DeviceState fromCompleteDevice(CompleteDevice completeDevice) {
+ // FIXME: we're sometimes getting back nulls in the list of functions... WUT?
+ // Once analytics are in place, look into adding something here so we know where
+ // this is coming from. In the meantime, filter out nulls from this list, since that's
+ // obviously doubleplusungood.
+ Set functions = set(Funcy.filter(completeDevice.functions, Funcy.notNull()));
+ Map variables = transformVariables(completeDevice);
+
+ return new DeviceState.DeviceStateBuilder(completeDevice.deviceId, functions, variables)
+ .name(completeDevice.name)
+ .cellular(completeDevice.cellular)
+ .connected(completeDevice.isConnected)
+ .version(completeDevice.version)
+ .deviceType(ParticleDeviceType.fromInt(completeDevice.productId))
+ .platformId(completeDevice.platformId)
+ .productId(completeDevice.productId)
+ .imei(completeDevice.imei)
+ .currentBuild(completeDevice.currentBuild)
+ .defaultBuild(completeDevice.defaultBuild)
+ .ipAddress(completeDevice.ipAddress)
+ .lastAppName(completeDevice.lastAppName)
+ .status(completeDevice.status)
+ .requiresUpdate(completeDevice.requiresUpdate)
+ .lastHeard(completeDevice.lastHeard)
+ .build();
+ }
+
+ // for offline devices
+ private DeviceState fromSimpleDeviceModel(Models.SimpleDevice offlineDevice) {
+ Set functions = new HashSet<>();
+ Map variables = new ArrayMap<>();
+
+ return new DeviceState.DeviceStateBuilder(offlineDevice.id, functions, variables)
+ .name(offlineDevice.name)
+ .cellular(offlineDevice.cellular)
+ .connected(offlineDevice.isConnected)
+ .version("")
+ .deviceType(ParticleDeviceType.fromInt(offlineDevice.productId))
+ .platformId(offlineDevice.platformId)
+ .productId(offlineDevice.productId)
+ .imei(offlineDevice.imei)
+ .currentBuild(offlineDevice.currentBuild)
+ .defaultBuild(offlineDevice.defaultBuild)
+ .ipAddress(offlineDevice.ipAddress)
+ .lastAppName("")
+ .status(offlineDevice.status)
+ .requiresUpdate(false)
+ .lastHeard(offlineDevice.lastHeard)
+ .build();
+ }
+
+
+ private static Map transformVariables(CompleteDevice completeDevice) {
+ if (completeDevice.variables == null) {
+ return Collections.emptyMap();
+ }
+
+ Map variables = new ArrayMap<>();
+
+ for (Entry entry : completeDevice.variables.entrySet()) {
+ if (!all(entry.getKey(), entry.getValue())) {
+ log.w(String.format(
+ "Found null key and/or value for variable in device $1%s. key=$2%s, value=$3%s",
+ completeDevice.name, entry.getKey(), entry.getValue()));
+ continue;
+ }
+
+ VariableType variableType = toVariableType.apply(entry.getValue());
+ if (variableType == null) {
+ log.w(String.format("Unknown variable type for device $1%s: '$2%s'",
+ completeDevice.name, entry.getKey()));
+ continue;
+ }
+
+ variables.put(entry.getKey(), variableType);
+ }
+
+ return variables;
+ }
+
+
+ private void pruneDeviceMap(List latestCloudDeviceList) {
+ synchronized (devices) {
+ // make a copy of the current keyset since we mutate `devices` below
+ PySet currentDeviceIds = set(devices.keySet());
+ PySet newDeviceIds = set(Funcy.transformList(latestCloudDeviceList, toDeviceId));
+ // quoting the Sets docs for this next operation:
+ // "The returned set contains all elements that are contained by set1 and
+ // not contained by set2"
+ // In short, this set is all the device IDs which we have in our devices map,
+ // but which we did not hear about in this latest update from the cloud
+ Set toRemove = currentDeviceIds.getDifference(newDeviceIds);
+ for (String deviceId : toRemove) {
+ devices.remove(deviceId);
+ }
+ }
+ }
+
+
+ private static final Func toDeviceId = input -> input.id;
+
+ private class TokenDelegate implements ParticleAccessToken.ParticleAccessTokenDelegate {
+
+ @Override
+ public void accessTokenExpiredAt(final ParticleAccessToken accessToken, Date expirationDate) {
+ // handle auto-renewal of expired access tokens by internal timer event
+ // If user is null, don't bother because we have no credentials.
+ if (user != null) {
+ try {
+ logIn(user.getUser(), user.getPassword());
+ return;
+
+ } catch (ParticleCloudException e) {
+ log.e("Error while trying to log in: ", e);
+ }
+ }
+
+ ParticleAccessToken.removeSession();
+ token = null;
+ }
+ }
+ //endregion
+
+ private static Func toVariableType = value -> {
+ if (value == null) {
+ return null;
+ }
+
+ switch (value) {
+ case "int32":
+ return VariableType.INT;
+ case "double":
+ return VariableType.DOUBLE;
+ case "string":
+ return VariableType.STRING;
+ default:
+ return null;
+ }
+ };
+
+
+ // FIXME: review and polish this. The more I think about it, the more I like it, but
+ // make sure it's what we _really_ want. Maybe apply it to the regular getDevices() too?
+ public static class PartialDeviceListResultException extends Exception {
+
+ final List devices;
+
+ public PartialDeviceListResultException(List devices, Exception cause) {
+ super(cause);
+ this.devices = devices;
+ }
+
+ public PartialDeviceListResultException(List devices, RetrofitError error) {
+ super(error);
+ this.devices = devices;
+ }
+
+ public PartialDeviceListResultException(List devices) {
+ super("Undefined error while fetching devices");
+ this.devices = devices;
+ }
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudException.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudException.java
new file mode 100644
index 0000000..9700597
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudException.java
@@ -0,0 +1,225 @@
+package io.particle.android.sdk.cloud;
+
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.utils.EZ;
+import io.particle.android.sdk.utils.ParticleInternalStringUtils;
+import io.particle.android.sdk.utils.TLog;
+import okio.BufferedSource;
+import okio.Okio;
+import retrofit.RetrofitError;
+
+import static io.particle.android.sdk.utils.Py.list;
+
+
+// Heavily inspired by RetrofitError, which we are mostly wrapping here, but
+// we're making our own exception to make it a checked exception, and to avoid
+// tying the API to a particular library used by the API's implementation
+@ParametersAreNonnullByDefault
+public class ParticleCloudException extends Exception {
+
+ private static final TLog log = TLog.get(ParticleCloudException.class);
+
+ /** Identifies the event kind which triggered a {@link ParticleCloudException}. */
+ public enum Kind {
+
+ /** An {@link java.io.IOException} occurred while communicating to the server. */
+ NETWORK,
+
+ /** An exception was thrown while (de)serializing a body. */
+ CONVERSION,
+
+ /** A non-200 HTTP status code was received from the server. */
+ HTTP,
+
+ /**
+ * An internal error occurred while attempting to execute a request. It is best practice to
+ * re-throw this exception so your application intentionally crashes.
+ */
+ UNEXPECTED
+ }
+
+
+ public static class ResponseErrorData {
+
+ private final int httpStatusCode;
+ private final InputStream httpBodyInputStream;
+
+ private String lazyLoadedBody;
+ private boolean isBodyLoaded;
+
+ ResponseErrorData(int httpStatusCode, InputStream httpBodyInputStream) {
+ this.httpStatusCode = httpStatusCode;
+ this.httpBodyInputStream = httpBodyInputStream;
+ }
+
+ public int getHttpStatusCode() {
+ return httpStatusCode;
+ }
+
+ /**
+ * @return response body as a String, or null if no body was returned.
+ */
+ public String getBody() {
+ if (!isBodyLoaded) {
+ isBodyLoaded = true;
+ lazyLoadedBody = loadBody();
+ }
+ return lazyLoadedBody;
+ }
+
+ private String loadBody() {
+ if (httpBodyInputStream == null) {
+ return null;
+ }
+ BufferedSource buffer = null;
+ try {
+ buffer = Okio.buffer(Okio.source(httpBodyInputStream));
+ return buffer.readUtf8();
+
+ } catch (IOException e) {
+ log.i("Error reading HTTP response body: ", e);
+ return null;
+
+ } finally {
+ EZ.closeThisThingOrMaybeDont(buffer);
+ }
+ }
+
+ }
+
+
+ private final RetrofitError innerError;
+ private final ResponseErrorData responseData;
+ private boolean checkedForServerErrorMsg = false;
+ private String serverErrorMessage;
+
+ public ParticleCloudException(Exception exception) {
+ super(exception);
+ // FIXME: ugly hack to get around even uglier bug.
+ this.innerError = RetrofitError.unexpectedError("(URL UNKNOWN)", exception);
+ this.responseData = null;
+ }
+
+ ParticleCloudException(RetrofitError innerError) {
+ this.innerError = innerError;
+ this.responseData = buildResponseData(innerError);
+ }
+
+ /**
+ * Response containing HTTP status code & body.
+ *
+ * May be null depending on the nature of the error.
+ */
+ public ResponseErrorData getResponseData() {
+ return responseData;
+ }
+
+ /** The event kind which triggered this error. */
+ public Kind getKind() {
+ return Kind.valueOf(innerError.getKind().toString());
+ }
+
+ /**
+ * Any server-provided error message. May be null.
+ *
+ * If the server sent multiple errors, they will be concatenated together with newline characters.
+ *
+ * @return server-provided error or null
+ */
+ public String getServerErrorMsg() {
+ if (!checkedForServerErrorMsg) {
+ checkedForServerErrorMsg = true;
+ serverErrorMessage = loadServerErrorMsg();
+ }
+ return serverErrorMessage;
+ }
+
+ /**
+ * Returns a server provided message, if found, else just returns the result of the inner
+ * exception's .getMessage()
+ */
+ public String getBestMessage() {
+ // FIXME: this isn't the right place for user-facing data
+ if (getKind() == Kind.NETWORK ) {
+ return "Unable to connect to the server.";
+
+ } else if (getKind() == Kind.UNEXPECTED) {
+ return "Unknown error communicating with server.";
+ }
+ String serverMsg = getServerErrorMsg();
+ return (serverMsg == null) ? getMessage() : serverMsg;
+ }
+
+ private String loadServerErrorMsg() {
+ if (responseData == null || responseData.getBody() == null) {
+ return null;
+ }
+ try {
+ JSONObject jsonObject = new JSONObject(responseData.getBody());
+
+ if (jsonObject.has("error_description")) {
+ return jsonObject.getString("error_description");
+
+ } else if (jsonObject.has("errors")) {
+ List errors = getErrors(jsonObject);
+ return errors.isEmpty() ? null : ParticleInternalStringUtils.join(errors, '\n');
+
+ } else if (jsonObject.has("error")) {
+ return jsonObject.getString("error");
+ }
+
+ } catch (JSONException e) {
+ }
+ return null;
+ }
+
+ private List getErrors(JSONObject jsonObject) throws JSONException {
+ List errors = list();
+ JSONArray jsonArray = jsonObject.getJSONArray("errors");
+ if (jsonArray == null || jsonArray.length() == 0) {
+ return errors;
+ }
+ for (int i=0; i < jsonArray.length(); i++){
+ String msg = null;
+
+ JSONObject msgObj = jsonArray.optJSONObject(i);
+ if (msgObj != null) {
+ msg = msgObj.getString("message");
+ } else {
+ msg = jsonArray.get(i).toString();
+ }
+
+ errors.add(msg);
+ }
+
+ return errors;
+ }
+
+
+ private ResponseErrorData buildResponseData(RetrofitError error) {
+ if (error.getResponse() == null) {
+ return null;
+ }
+
+ InputStream in = null;
+ if (error.getResponse().getBody() != null) {
+ try {
+ in = error.getResponse().getBody().in();
+ } catch (IOException e) {
+ // Yo, dawg, I heard you like error handling in your error handling...
+ }
+ }
+ return new ResponseErrorData(error.getResponse().getStatus(), in);
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudSDK.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudSDK.java
new file mode 100644
index 0000000..5144073
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleCloudSDK.java
@@ -0,0 +1,80 @@
+package io.particle.android.sdk.cloud;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.ApiFactory.OauthBasicAuthCredentialsProvider;
+import io.particle.android.sdk.utils.TLog;
+
+/**
+ * Entry point for the Particle Cloud SDK.
+ */
+@ParametersAreNonnullByDefault
+public class ParticleCloudSDK {
+ // NOTE: pay attention to the interface, try to ignore the implementation, it's going to change.
+
+ /**
+ * Initialize the cloud SDK. Must be called somewhere in your Application.onCreate()
+ *
+ * (or anywhere else before your first Activity.onCreate() is called)
+ */
+ public static void init(Context ctx) {
+ initWithParams(ctx, null);
+ }
+
+ public static void initWithOauthCredentialsProvider(
+ Context ctx, @Nullable OauthBasicAuthCredentialsProvider oauthProvider) {
+ initWithParams(ctx, oauthProvider);
+ }
+
+ public static ParticleCloud getCloud() {
+ verifyInitCalled();
+ return instance.sdkProvider.getParticleCloud();
+ }
+
+
+ // NOTE: This is closer to the interface I'd like to provide eventually
+ static void initWithParams(Context ctx,
+ @Nullable OauthBasicAuthCredentialsProvider oauthProvider) {
+ if (instance != null) {
+ log.w("Calling ParticleCloudSDK.init() more than once does not re-initialize the SDK.");
+ return;
+ }
+
+ Context appContext = ctx.getApplicationContext();
+ SDKProvider sdkProvider = new SDKProvider(appContext, oauthProvider);
+ instance = new ParticleCloudSDK(sdkProvider);
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ static boolean isInitialized() {
+ return instance != null;
+ }
+
+ static SDKProvider getSdkProvider() {
+ verifyInitCalled();
+ return instance.sdkProvider;
+ }
+
+ static void verifyInitCalled() {
+ if (!isInitialized()) {
+ throw new IllegalStateException("init not called before using the Particle SDK. "
+ + "Are you calling ParticleCloudSDK.init() in your Application.onCreate()?");
+ }
+ }
+
+
+ private static final TLog log = TLog.get(ParticleCloudSDK.class);
+
+ private static ParticleCloudSDK instance;
+
+
+ private final SDKProvider sdkProvider;
+
+ private ParticleCloudSDK(SDKProvider sdkProvider) {
+ this.sdkProvider = sdkProvider;
+ }
+
+}
diff --git a/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleDevice.java b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleDevice.java
new file mode 100644
index 0000000..158db57
--- /dev/null
+++ b/cloudsdk/src/main/java/io/particle/android/sdk/cloud/ParticleDevice.java
@@ -0,0 +1,690 @@
+package io.particle.android.sdk.cloud;
+
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import io.particle.android.sdk.cloud.Responses.ReadDoubleVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadIntVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadObjectVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadStringVariableResponse;
+import io.particle.android.sdk.cloud.Responses.ReadVariableResponse;
+import io.particle.android.sdk.cloud.models.DeviceStateChange;
+import io.particle.android.sdk.utils.ParticleInternalStringUtils;
+import io.particle.android.sdk.utils.Preconditions;
+import io.particle.android.sdk.utils.TLog;
+import okio.Okio;
+import retrofit.RetrofitError;
+import retrofit.mime.TypedByteArray;
+import retrofit.mime.TypedFile;
+
+import static io.particle.android.sdk.utils.Py.list;
+
+
+// don't warn about public APIs not being referenced inside this module, or about
+// the _default locale_ in a bunch of backend code
+@SuppressLint("DefaultLocale")
+@SuppressWarnings({"UnusedDeclaration"})
+@ParametersAreNonnullByDefault
+public class ParticleDevice implements Parcelable {
+
+ public enum ParticleDeviceType {
+ CORE,
+ PHOTON,
+ ELECTRON;
+
+ public static ParticleDeviceType fromInt(int intValue) {
+ switch (intValue) {
+ case 0:
+ return CORE;
+ case 10:
+ return ELECTRON;
+ case 5:
+ case 6:
+ default:
+ return PHOTON;
+ }
+ }
+ }
+
+ public enum ParticleDeviceState {
+ CAME_ONLINE,
+ FLASH_STARTED,
+ FLASH_SUCCEEDED,
+ FLASH_FAILED,
+ APP_HASH_UPDATED,
+ ENTERED_SAFE_MODE,
+ SAFE_MODE_UPDATER,
+ WENT_OFFLINE,
+ UNKNOWN
+ }
+
+ public enum VariableType {
+ INT,
+ DOUBLE,
+ STRING
+ }
+
+
+ public static class FunctionDoesNotExistException extends Exception {
+
+ public FunctionDoesNotExistException(String functionName) {
+ super("Function " + functionName + " does not exist on this device");
+ }
+ }
+
+
+ public static class VariableDoesNotExistException extends Exception {
+
+ public VariableDoesNotExistException(String variableName) {
+ super("Variable " + variableName + " does not exist on this device");
+ }
+ }
+
+
+ public enum KnownApp {
+ TINKER("tinker");
+
+ private final String appName;
+
+ KnownApp(String appName) {
+ this.appName = appName;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+ }
+
+ private static final int MAX_PARTICLE_FUNCTION_ARG_LENGTH = 63;
+
+ private static final TLog log = TLog.get(ParticleDevice.class);
+
+ private final CopyOnWriteArrayList subscriptions = new CopyOnWriteArrayList<>();
+ private final ApiDefs.CloudApi mainApi;
+ private final ParticleCloud cloud;
+
+ volatile DeviceState deviceState;
+
+ private volatile boolean isFlashing = false;
+
+ ParticleDevice(ApiDefs.CloudApi mainApi, ParticleCloud cloud, DeviceState deviceState) {
+ this.mainApi = mainApi;
+ this.cloud = cloud;
+ this.deviceState = deviceState;
+ }
+
+ /**
+ * Device ID string
+ */
+ public String getID() {
+ return deviceState.deviceId;
+ }
+
+ /**
+ * Device name. Device can be renamed in the cloud via #setName(String)
+ */
+ public String getName() {
+ return deviceState.name;
+ }
+
+ /**
+ * Rename the device in the cloud. If renaming fails name will stay the same.
+ */
+ public void setName(String newName) throws ParticleCloudException {
+ cloud.rename(this.deviceState.deviceId, newName);
+ }
+
+ /**
+ * Is device connected to the cloud?
+ */
+ public boolean isConnected() {
+ return deviceState.isConnected;
+ }
+
+ /**
+ * Get an immutable set of all the function names exposed by device
+ */
+ public Set getFunctions() {
+ // no need for a defensive copy, this is an immutable set
+ return deviceState.functions;
+ }
+
+ /**
+ * Get an immutable map of exposed variables on device with their respective types.
+ */
+ public Map getVariables() {
+ // no need for a defensive copy, this is an immutable set
+ return deviceState.variables;
+ }
+
+ /**
+ * Device firmware version string
+ */
+ public String getVersion() {
+ return deviceState.version;
+ }
+
+ public boolean requiresUpdate() {
+ return deviceState.requiresUpdate;
+ }
+
+ public ParticleDeviceType getDeviceType() {
+ return deviceState.deviceType;
+ }
+
+ public int getPlatformID() {
+ return deviceState.platformId;
+ }
+
+ public int getProductID() {
+ return deviceState.productId;
+ }
+
+ public boolean isCellular() {
+ return deviceState.cellular;
+ }
+
+ public String getImei() {
+ return deviceState.imei;
+ }
+
+ public String getCurrentBuild() {
+ return deviceState.currentBuild;
+ }
+
+ public String getDefaultBuild() {
+ return deviceState.defaultBuild;
+ }
+
+ public String getIpAddress() {
+ return deviceState.ipAddress;
+ }
+
+ public String getLastAppName() {
+ return deviceState.lastAppName;
+ }
+
+ public String getStatus() {
+ return deviceState.status;
+ }
+
+ public Date getLastHeard() {
+ return deviceState.lastHeard;
+ }
+
+ /**
+ * Return the value for variableName on this Particle device.
+ *
+ * Unless you specifically require generic handling, it is recommended that you use the
+ * get(type)Variable methods instead, e.g.: getIntVariable().
+ * These type-specific methods don't require extra casting or type checking on your part, and
+ * they more clearly and succinctly express your intent.
+ */
+ @WorkerThread
+ public Object getVariable(String variableName)
+ throws ParticleCloudException, IOException, VariableDoesNotExistException {
+
+ VariableRequester