diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..e76c77c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100755
index 0000000..2715a34
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100755
index 0000000..f5f1ec4
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100755
index 0000000..f797995
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100755
index 0000000..9b770a6
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100755
index 0000000..9661ac7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100755
index 0000000..3543521
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100755
index 0000000..203a3f8
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "us.keiran.suitleds"
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100755
index 0000000..6e7ffa9
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/us/keiran/suitleds/ExampleInstrumentedTest.java b/app/src/androidTest/java/us/keiran/suitleds/ExampleInstrumentedTest.java
new file mode 100755
index 0000000..970cc48
--- /dev/null
+++ b/app/src/androidTest/java/us/keiran/suitleds/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package us.keiran.suitleds;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("us.keiran.suitleds", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..fef7d8c
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/us/keiran/suitleds/DeviceListActivity.java b/app/src/main/java/us/keiran/suitleds/DeviceListActivity.java
new file mode 100755
index 0000000..deb5297
--- /dev/null
+++ b/app/src/main/java/us/keiran/suitleds/DeviceListActivity.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package us.keiran.suitleds;
+
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+
+
+
+ import android.app.Activity;
+ import android.bluetooth.BluetoothAdapter;
+ import android.bluetooth.BluetoothDevice;
+ import android.bluetooth.BluetoothManager;
+ import android.content.BroadcastReceiver;
+ import android.content.ComponentName;
+ import android.content.Context;
+ import android.content.Intent;
+ import android.content.IntentFilter;
+ import android.content.ServiceConnection;
+ import android.content.pm.PackageManager;
+ import android.graphics.Color;
+ import android.os.Bundle;
+ import android.os.Handler;
+ import android.os.IBinder;
+ import android.os.Message;
+ import android.util.Log;
+ import android.view.Gravity;
+ import android.view.LayoutInflater;
+ import android.view.View;
+ import android.view.View.OnClickListener;
+ import android.view.ViewGroup;
+ import android.view.Window;
+ import android.widget.AdapterView;
+ import android.widget.AdapterView.OnItemClickListener;
+ import android.widget.BaseAdapter;
+ import android.widget.Button;
+ import android.widget.ListView;
+ import android.widget.TextView;
+ import android.widget.Toast;
+
+public class DeviceListActivity extends Activity {
+ private BluetoothAdapter mBluetoothAdapter;
+
+ // private BluetoothAdapter mBtAdapter;
+ private TextView mEmptyList;
+ public static final String TAG = "DeviceListActivity";
+
+ List deviceList;
+ private DeviceAdapter deviceAdapter;
+ private ServiceConnection onService = null;
+ Map devRssiValues;
+ private static final long SCAN_PERIOD = 10000; //10 seconds
+ private Handler mHandler;
+ private boolean mScanning;
+
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate");
+ getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_bar);
+ setContentView(R.layout.device_list);
+ android.view.WindowManager.LayoutParams layoutParams = this.getWindow().getAttributes();
+ layoutParams.gravity=Gravity.TOP;
+ layoutParams.y = 200;
+ mHandler = new Handler();
+ // Use this check to determine whether BLE is supported on the device. Then you can
+ // selectively disable BLE-related features.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
+ // BluetoothAdapter through BluetoothManager.
+ final BluetoothManager bluetoothManager =
+ (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ mBluetoothAdapter = bluetoothManager.getAdapter();
+
+ // Checks if Bluetooth is supported on the device.
+ if (mBluetoothAdapter == null) {
+ Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+ populateList();
+ mEmptyList = (TextView) findViewById(R.id.empty);
+ Button cancelButton = (Button) findViewById(R.id.btn_cancel);
+ cancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (mScanning==false) scanLeDevice(true);
+ else finish();
+ }
+ });
+
+ }
+
+ private void populateList() {
+ /* Initialize device list container */
+ Log.d(TAG, "populateList");
+ deviceList = new ArrayList();
+ deviceAdapter = new DeviceAdapter(this, deviceList);
+ devRssiValues = new HashMap();
+
+ ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
+ newDevicesListView.setAdapter(deviceAdapter);
+ newDevicesListView.setOnItemClickListener(mDeviceClickListener);
+
+ scanLeDevice(true);
+
+ }
+
+ private void scanLeDevice(final boolean enable) {
+ final Button cancelButton = (Button) findViewById(R.id.btn_cancel);
+ if (enable) {
+ // Stops scanning after a pre-defined scan period.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScanning = false;
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+
+ cancelButton.setText(R.string.scan);
+
+ }
+ }, SCAN_PERIOD);
+
+ mScanning = true;
+ mBluetoothAdapter.startLeScan(mLeScanCallback);
+ cancelButton.setText(R.string.cancel);
+ } else {
+ mScanning = false;
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+ cancelButton.setText(R.string.scan);
+ }
+
+ }
+
+ private BluetoothAdapter.LeScanCallback mLeScanCallback =
+ new BluetoothAdapter.LeScanCallback() {
+
+ @Override
+ public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ addDevice(device,rssi);
+ }
+ });
+
+ }
+ });
+ }
+ };
+
+ private void addDevice(BluetoothDevice device, int rssi) {
+ boolean deviceFound = false;
+
+ for (BluetoothDevice listDev : deviceList) {
+ if (listDev.getAddress().equals(device.getAddress())) {
+ deviceFound = true;
+ break;
+ }
+ }
+
+
+ devRssiValues.put(device.getAddress(), rssi);
+ if (!deviceFound) {
+ deviceList.add(device);
+ mEmptyList.setVisibility(View.GONE);
+
+
+
+
+ deviceAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
+ filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+
+ }
+
+ private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ BluetoothDevice device = deviceList.get(position);
+ mBluetoothAdapter.stopLeScan(mLeScanCallback);
+
+ Bundle b = new Bundle();
+ b.putString(BluetoothDevice.EXTRA_DEVICE, deviceList.get(position).getAddress());
+
+ Intent result = new Intent();
+ result.putExtras(b);
+ setResult(Activity.RESULT_OK, result);
+ finish();
+
+ }
+ };
+
+
+
+ protected void onPause() {
+ super.onPause();
+ scanLeDevice(false);
+ }
+
+ class DeviceAdapter extends BaseAdapter {
+ Context context;
+ List devices;
+ LayoutInflater inflater;
+
+ public DeviceAdapter(Context context, List devices) {
+ this.context = context;
+ inflater = LayoutInflater.from(context);
+ this.devices = devices;
+ }
+
+ @Override
+ public int getCount() {
+ return devices.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return devices.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewGroup vg;
+
+ if (convertView != null) {
+ vg = (ViewGroup) convertView;
+ } else {
+ vg = (ViewGroup) inflater.inflate(R.layout.device_element, null);
+ }
+
+ BluetoothDevice device = devices.get(position);
+ final TextView tvadd = ((TextView) vg.findViewById(R.id.address));
+ final TextView tvname = ((TextView) vg.findViewById(R.id.name));
+ final TextView tvpaired = (TextView) vg.findViewById(R.id.paired);
+ final TextView tvrssi = (TextView) vg.findViewById(R.id.rssi);
+
+ tvrssi.setVisibility(View.VISIBLE);
+ byte rssival = (byte) devRssiValues.get(device.getAddress()).intValue();
+ if (rssival != 0) {
+ tvrssi.setText("Rssi = " + String.valueOf(rssival));
+ }
+
+ tvname.setText(device.getName());
+ tvadd.setText(device.getAddress());
+ if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
+ Log.i(TAG, "device::"+device.getName());
+ tvname.setTextColor(Color.WHITE);
+ tvadd.setTextColor(Color.WHITE);
+ tvpaired.setTextColor(Color.GRAY);
+ tvpaired.setVisibility(View.VISIBLE);
+ tvpaired.setText(R.string.paired);
+ tvrssi.setVisibility(View.VISIBLE);
+ tvrssi.setTextColor(Color.WHITE);
+
+ } else {
+ tvname.setTextColor(Color.WHITE);
+ tvadd.setTextColor(Color.WHITE);
+ tvpaired.setVisibility(View.GONE);
+ tvrssi.setVisibility(View.VISIBLE);
+ tvrssi.setTextColor(Color.WHITE);
+ }
+ return vg;
+ }
+ }
+ private void showMessage(String msg) {
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/app/src/main/java/us/keiran/suitleds/MainActivity.java b/app/src/main/java/us/keiran/suitleds/MainActivity.java
new file mode 100755
index 0000000..995c89a
--- /dev/null
+++ b/app/src/main/java/us/keiran/suitleds/MainActivity.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package us.keiran.suitleds;
+
+ import java.io.UnsupportedEncodingException;
+ import java.text.DateFormat;
+ import java.util.Date;
+
+
+ import us.keiran.suitleds.UartService;
+
+ import android.Manifest;
+ import android.app.Activity;
+ import android.app.AlertDialog;
+ import android.bluetooth.BluetoothAdapter;
+ import android.bluetooth.BluetoothDevice;
+ import android.bluetooth.BluetoothManager;
+
+ import android.content.BroadcastReceiver;
+ import android.content.ComponentName;
+ import android.content.Context;
+ import android.content.DialogInterface;
+ import android.content.Intent;
+ import android.content.IntentFilter;
+ import android.content.ServiceConnection;
+ import android.content.res.Configuration;
+ import android.media.Ringtone;
+ import android.media.RingtoneManager;
+ import android.net.Uri;
+ import android.os.Build;
+ import android.os.Bundle;
+ import android.os.Handler;
+ import android.os.IBinder;
+ import android.os.Message;
+ import android.support.v4.content.LocalBroadcastManager;
+ import android.util.Log;
+ import android.view.Gravity;
+ import android.view.View;
+ import android.widget.ArrayAdapter;
+ import android.widget.Button;
+ import android.widget.EditText;
+ import android.widget.LinearLayout;
+ import android.widget.ListView;
+ import android.widget.RadioGroup;
+ import android.widget.TextView;
+ import android.widget.Toast;
+
+public class MainActivity extends Activity implements RadioGroup.OnCheckedChangeListener {
+ private static final int REQUEST_SELECT_DEVICE = 1;
+ private static final int REQUEST_ENABLE_BT = 2;
+ private static final int UART_PROFILE_READY = 10;
+ public static final String TAG = "nRFUART";
+ private static final int UART_PROFILE_CONNECTED = 20;
+ private static final int UART_PROFILE_DISCONNECTED = 21;
+ private static final int PERMISSION_REQUEST_COARSE_LOCATION = 456;
+ private static final int STATE_OFF = 10;
+
+ TextView mRemoteRssiVal;
+ RadioGroup mRg;
+ private int mState = UART_PROFILE_DISCONNECTED;
+ private UartService mService = null;
+ private BluetoothDevice mDevice = null;
+ private BluetoothAdapter mBtAdapter = null;
+ private ListView messageListView;
+ private ArrayAdapter listAdapter;
+ private Button btnConnectDisconnect, btnSend, btnRainbow;
+ private EditText edtMessage;
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
+ }
+ setContentView(R.layout.main);
+ mBtAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBtAdapter == null) {
+ Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+ messageListView = (ListView) findViewById(R.id.listMessage);
+ listAdapter = new ArrayAdapter(this, R.layout.message_detail);
+ messageListView.setAdapter(listAdapter);
+ messageListView.setDivider(null);
+ btnConnectDisconnect=(Button) findViewById(R.id.btn_select);
+ btnSend=(Button) findViewById(R.id.sendButton);
+ btnRainbow=findViewById(R.id.rainbowButton);
+ edtMessage = (EditText) findViewById(R.id.sendText);
+ service_init();
+
+
+
+ // Handler Disconnect & Connect button
+ btnConnectDisconnect.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mBtAdapter.isEnabled()) {
+ Log.i(TAG, "onClick - BT not enabled yet");
+ Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+ }
+ else {
+ if (btnConnectDisconnect.getText().equals("Connect")){
+
+ //Connect button pressed, open DeviceListActivity class, with popup windows that scan for devices
+
+ Intent newIntent = new Intent(MainActivity.this, DeviceListActivity.class);
+ startActivityForResult(newIntent, REQUEST_SELECT_DEVICE);
+ } else {
+ //Disconnect button pressed
+ if (mDevice!=null)
+ {
+ mService.disconnect();
+
+ }
+ }
+ }
+ }
+ });
+ // Handler Send button
+ btnSend.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ EditText editText = (EditText) findViewById(R.id.sendText);
+ String message = editText.getText().toString();
+ byte[] value;
+ try {
+ //send data to service
+ value = message.getBytes("UTF-8");
+ mService.writeRXCharacteristic(value);
+ //Update the log with time stamp
+ String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
+ listAdapter.add("["+currentDateTimeString+"] TX: "+ message);
+ messageListView.smoothScrollToPosition(listAdapter.getCount() - 1);
+ edtMessage.setText("");
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ });
+
+ btnRainbow.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ byte[] value;
+ try {
+ //send data to service
+ value = "!PR".getBytes("UTF-8");
+ mService.sendDataWithCRC(value);
+ //Update the log with time stamp
+ String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
+ listAdapter.add("["+currentDateTimeString+"] TX: "+ "!PR");
+ messageListView.smoothScrollToPosition(listAdapter.getCount() - 1);
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ });
+
+ // Set initial UI state
+
+ }
+
+ //UART service connected/disconnected
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder rawBinder) {
+ mService = ((UartService.LocalBinder) rawBinder).getService();
+ Log.d(TAG, "onServiceConnected mService= " + mService);
+ if (!mService.initialize()) {
+ Log.e(TAG, "Unable to initialize Bluetooth");
+ finish();
+ }
+
+ }
+
+ public void onServiceDisconnected(ComponentName classname) {
+ //// mService.disconnect(mDevice);
+ mService = null;
+ }
+ };
+
+ private Handler mHandler = new Handler() {
+ @Override
+
+ //Handler events that received from UART service
+ public void handleMessage(Message msg) {
+
+ }
+ };
+
+ private final BroadcastReceiver UARTStatusChangeReceiver = new BroadcastReceiver() {
+
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ final Intent mIntent = intent;
+ //*********************//
+ if (action.equals(UartService.ACTION_GATT_CONNECTED)) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
+ Log.d(TAG, "UART_CONNECT_MSG");
+ btnConnectDisconnect.setText("Disconnect");
+ edtMessage.setEnabled(true);
+ btnSend.setEnabled(true);
+ ((TextView) findViewById(R.id.deviceName)).setText(mDevice.getName()+ " - ready");
+ listAdapter.add("["+currentDateTimeString+"] Connected to: "+ mDevice.getName());
+ messageListView.smoothScrollToPosition(listAdapter.getCount() - 1);
+ mState = UART_PROFILE_CONNECTED;
+ }
+ });
+ }
+
+ //*********************//
+ if (action.equals(UartService.ACTION_GATT_DISCONNECTED)) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
+ Log.d(TAG, "UART_DISCONNECT_MSG");
+ btnConnectDisconnect.setText("Connect");
+ edtMessage.setEnabled(false);
+ btnSend.setEnabled(false);
+ ((TextView) findViewById(R.id.deviceName)).setText("Not Connected");
+ listAdapter.add("["+currentDateTimeString+"] Disconnected to: "+ mDevice.getName());
+ mState = UART_PROFILE_DISCONNECTED;
+ mService.close();
+ //setUiState();
+
+ }
+ });
+ }
+
+
+ //*********************//
+ if (action.equals(UartService.ACTION_GATT_SERVICES_DISCOVERED)) {
+ mService.enableTXNotification();
+ }
+ //*********************//
+ if (action.equals(UartService.ACTION_DATA_AVAILABLE)) {
+
+ final byte[] txValue = intent.getByteArrayExtra(UartService.EXTRA_DATA);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ try {
+ String text = new String(txValue, "UTF-8");
+ String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
+ listAdapter.add("["+currentDateTimeString+"] RX: "+text);
+ messageListView.smoothScrollToPosition(listAdapter.getCount() - 1);
+
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ });
+ }
+ //*********************//
+ if (action.equals(UartService.DEVICE_DOES_NOT_SUPPORT_UART)){
+ showMessage("Device doesn't support UART. Disconnecting");
+ mService.disconnect();
+ }
+
+
+ }
+ };
+
+ private void service_init() {
+ Intent bindIntent = new Intent(this, UartService.class);
+ bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(UARTStatusChangeReceiver, makeGattUpdateIntentFilter());
+ }
+ private static IntentFilter makeGattUpdateIntentFilter() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(UartService.ACTION_GATT_CONNECTED);
+ intentFilter.addAction(UartService.ACTION_GATT_DISCONNECTED);
+ intentFilter.addAction(UartService.ACTION_GATT_SERVICES_DISCOVERED);
+ intentFilter.addAction(UartService.ACTION_DATA_AVAILABLE);
+ intentFilter.addAction(UartService.DEVICE_DOES_NOT_SUPPORT_UART);
+ return intentFilter;
+ }
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "onDestroy()");
+
+ try {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(UARTStatusChangeReceiver);
+ } catch (Exception ignore) {
+ Log.e(TAG, ignore.toString());
+ }
+ unbindService(mServiceConnection);
+ mService.stopSelf();
+ mService= null;
+
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "onStop");
+ super.onStop();
+ }
+
+ @Override
+ protected void onPause() {
+ Log.d(TAG, "onPause");
+ super.onPause();
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ Log.d(TAG, "onRestart");
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume");
+ if (!mBtAdapter.isEnabled()) {
+ Log.i(TAG, "onResume - BT not enabled yet");
+ Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+ }
+
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+
+ case REQUEST_SELECT_DEVICE:
+ //When the DeviceListActivity return, with the selected device address
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String deviceAddress = data.getStringExtra(BluetoothDevice.EXTRA_DEVICE);
+ mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
+
+ Log.d(TAG, "... onActivityResultdevice.address==" + mDevice + "mserviceValue" + mService);
+ ((TextView) findViewById(R.id.deviceName)).setText(mDevice.getName()+ " - connecting");
+ mService.connect(deviceAddress);
+
+
+ }
+ break;
+ case REQUEST_ENABLE_BT:
+ // When the request to enable Bluetooth returns
+ if (resultCode == Activity.RESULT_OK) {
+ Toast.makeText(this, "Bluetooth has turned on ", Toast.LENGTH_SHORT).show();
+
+ } else {
+ // User did not enable Bluetooth or an error occurred
+ Log.d(TAG, "BT not enabled");
+ Toast.makeText(this, "Problem in BT Turning ON ", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ break;
+ default:
+ Log.e(TAG, "wrong request code");
+ break;
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+
+ }
+
+
+ private void showMessage(String msg) {
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mState == UART_PROFILE_CONNECTED) {
+ Intent startMain = new Intent(Intent.ACTION_MAIN);
+ startMain.addCategory(Intent.CATEGORY_HOME);
+ startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(startMain);
+ showMessage("nRFUART's running in background.\n Disconnect to exit");
+ }
+ else {
+ new AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.popup_title)
+ .setMessage(R.string.popup_message)
+ .setPositiveButton(R.string.popup_yes, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.popup_no, null)
+ .show();
+ }
+ }
+}
diff --git a/app/src/main/java/us/keiran/suitleds/UartService.java b/app/src/main/java/us/keiran/suitleds/UartService.java
new file mode 100755
index 0000000..b9d7cd4
--- /dev/null
+++ b/app/src/main/java/us/keiran/suitleds/UartService.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package us.keiran.suitleds;
+
+ import android.app.Service;
+ import android.bluetooth.BluetoothAdapter;
+ import android.bluetooth.BluetoothDevice;
+ import android.bluetooth.BluetoothGatt;
+ import android.bluetooth.BluetoothGattCallback;
+ import android.bluetooth.BluetoothGattCharacteristic;
+ import android.bluetooth.BluetoothGattDescriptor;
+ import android.bluetooth.BluetoothGattService;
+ import android.bluetooth.BluetoothManager;
+ import android.bluetooth.BluetoothProfile;
+ import android.content.Context;
+ import android.content.Intent;
+ import android.os.Binder;
+ import android.os.IBinder;
+ import android.support.v4.content.LocalBroadcastManager;
+ import android.util.Log;
+
+ import java.util.List;
+ import java.util.UUID;
+
+/**
+ * Service for managing connection and data communication with a GATT server hosted on a
+ * given Bluetooth LE device.
+ */
+public class UartService extends Service {
+ private final static String TAG = UartService.class.getSimpleName();
+
+ private BluetoothManager mBluetoothManager;
+ private BluetoothAdapter mBluetoothAdapter;
+ private String mBluetoothDeviceAddress;
+ private BluetoothGatt mBluetoothGatt;
+ private int mConnectionState = STATE_DISCONNECTED;
+
+ private static final int STATE_DISCONNECTED = 0;
+ private static final int STATE_CONNECTING = 1;
+ private static final int STATE_CONNECTED = 2;
+
+ public final static String ACTION_GATT_CONNECTED =
+ "com.nordicsemi.nrfUART.ACTION_GATT_CONNECTED";
+ public final static String ACTION_GATT_DISCONNECTED =
+ "com.nordicsemi.nrfUART.ACTION_GATT_DISCONNECTED";
+ public final static String ACTION_GATT_SERVICES_DISCOVERED =
+ "com.nordicsemi.nrfUART.ACTION_GATT_SERVICES_DISCOVERED";
+ public final static String ACTION_DATA_AVAILABLE =
+ "com.nordicsemi.nrfUART.ACTION_DATA_AVAILABLE";
+ public final static String EXTRA_DATA =
+ "com.nordicsemi.nrfUART.EXTRA_DATA";
+ public final static String DEVICE_DOES_NOT_SUPPORT_UART =
+ "com.nordicsemi.nrfUART.DEVICE_DOES_NOT_SUPPORT_UART";
+
+ public static final UUID TX_POWER_UUID = UUID.fromString("00001804-0000-1000-8000-00805f9b34fb");
+ public static final UUID TX_POWER_LEVEL_UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb");
+ public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
+ public static final UUID FIRMWARE_REVISON_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
+ public static final UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
+ public static final UUID RX_SERVICE_UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
+ public static final UUID RX_CHAR_UUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
+ public static final UUID TX_CHAR_UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
+
+
+ // Implements callback methods for GATT events that the app cares about. For example,
+ // connection change and services discovered.
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ String intentAction;
+
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ intentAction = ACTION_GATT_CONNECTED;
+ mConnectionState = STATE_CONNECTED;
+ broadcastUpdate(intentAction);
+ Log.i(TAG, "Connected to GATT server.");
+ // Attempts to discover services after successful connection.
+ Log.i(TAG, "Attempting to start service discovery:" +
+ mBluetoothGatt.discoverServices());
+
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ intentAction = ACTION_GATT_DISCONNECTED;
+ mConnectionState = STATE_DISCONNECTED;
+ Log.i(TAG, "Disconnected from GATT server.");
+ broadcastUpdate(intentAction);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ Log.w(TAG, "mBluetoothGatt = " + mBluetoothGatt );
+
+ broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
+ }
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
+ }
+ };
+
+ private void broadcastUpdate(final String action) {
+ final Intent intent = new Intent(action);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
+ private void broadcastUpdate(final String action,
+ final BluetoothGattCharacteristic characteristic) {
+ final Intent intent = new Intent(action);
+
+ // This is special handling for the Heart Rate Measurement profile. Data parsing is
+ // carried out as per profile specifications:
+ // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
+ if (TX_CHAR_UUID.equals(characteristic.getUuid())) {
+
+ // Log.d(TAG, String.format("Received TX: %d",characteristic.getValue() ));
+ intent.putExtra(EXTRA_DATA, characteristic.getValue());
+ } else {
+
+ }
+ LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+ }
+
+ public class LocalBinder extends Binder {
+ UartService getService() {
+ return UartService.this;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ // After using a given device, you should make sure that BluetoothGatt.close() is called
+ // such that resources are cleaned up properly. In this particular example, close() is
+ // invoked when the UI is disconnected from the Service.
+ close();
+ return super.onUnbind(intent);
+ }
+
+ private final IBinder mBinder = new LocalBinder();
+
+ /**
+ * Initializes a reference to the local Bluetooth adapter.
+ *
+ * @return Return true if the initialization is successful.
+ */
+ public boolean initialize() {
+ // For API level 18 and above, get a reference to BluetoothAdapter through
+ // BluetoothManager.
+ if (mBluetoothManager == null) {
+ mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mBluetoothManager == null) {
+ Log.e(TAG, "Unable to initialize BluetoothManager.");
+ return false;
+ }
+ }
+
+ mBluetoothAdapter = mBluetoothManager.getAdapter();
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Connects to the GATT server hosted on the Bluetooth LE device.
+ *
+ * @param address The device address of the destination device.
+ *
+ * @return Return true if the connection is initiated successfully. The connection result
+ * is reported asynchronously through the
+ * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
+ * callback.
+ */
+ public boolean connect(final String address) {
+ if (mBluetoothAdapter == null || address == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
+ return false;
+ }
+
+ // Previously connected device. Try to reconnect.
+ if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
+ && mBluetoothGatt != null) {
+ Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
+ if (mBluetoothGatt.connect()) {
+ mConnectionState = STATE_CONNECTING;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
+ if (device == null) {
+ Log.w(TAG, "Device not found. Unable to connect.");
+ return false;
+ }
+ // We want to directly connect to the device, so we are setting the autoConnect
+ // parameter to false.
+ mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
+ Log.d(TAG, "Trying to create a new connection.");
+ mBluetoothDeviceAddress = address;
+ mConnectionState = STATE_CONNECTING;
+ return true;
+ }
+
+ /**
+ * Disconnects an existing connection or cancel a pending connection. The disconnection result
+ * is reported asynchronously through the
+ * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
+ * callback.
+ */
+ public void disconnect() {
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized");
+ return;
+ }
+ mBluetoothGatt.disconnect();
+ // mBluetoothGatt.close();
+ }
+
+ /**
+ * After using a given BLE device, the app must call this method to ensure resources are
+ * released properly.
+ */
+ public void close() {
+ if (mBluetoothGatt == null) {
+ return;
+ }
+ Log.w(TAG, "mBluetoothGatt closed");
+ mBluetoothDeviceAddress = null;
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+
+ /**
+ * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
+ * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
+ * callback.
+ *
+ * @param characteristic The characteristic to read from.
+ */
+ public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized");
+ return;
+ }
+ mBluetoothGatt.readCharacteristic(characteristic);
+ }
+
+ /**
+ * Enables or disables notification on a give characteristic.
+ *
+ * @param characteristic Characteristic to act on.
+ * @param enabled If true, enable notification. False otherwise.
+ */
+ /*
+ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enabled) {
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ Log.w(TAG, "BluetoothAdapter not initialized");
+ return;
+ }
+ mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
+
+
+ if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+ }*/
+
+ /**
+ * Enable TXNotification
+ *
+ * @return
+ */
+ public void enableTXNotification()
+ {
+ /*
+ if (mBluetoothGatt == null) {
+ showMessage("mBluetoothGatt null" + mBluetoothGatt);
+ broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
+ return;
+ }
+ */
+ BluetoothGattService RxService = mBluetoothGatt.getService(RX_SERVICE_UUID);
+ if (RxService == null) {
+ showMessage("Rx service not found!");
+ broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
+ return;
+ }
+ BluetoothGattCharacteristic TxChar = RxService.getCharacteristic(TX_CHAR_UUID);
+ if (TxChar == null) {
+ showMessage("Tx charateristic not found!");
+ broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
+ return;
+ }
+ mBluetoothGatt.setCharacteristicNotification(TxChar,true);
+
+ BluetoothGattDescriptor descriptor = TxChar.getDescriptor(CCCD);
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+
+ }
+
+ public void sendDataWithCRC(byte[] data) {
+
+ // Calculate checksum
+ byte checksum = 0;
+ for (byte aData : data) {
+ checksum += aData;
+ }
+ checksum = (byte) (~checksum); // Invert
+
+ // Add crc to data
+ byte dataCrc[] = new byte[data.length + 1];
+ System.arraycopy(data, 0, dataCrc, 0, data.length);
+ dataCrc[data.length] = checksum;
+
+ // Send it
+ writeRXCharacteristic(dataCrc);
+ }
+
+ public void writeRXCharacteristic(byte[] value)
+ {
+
+ BluetoothGattService RxService = mBluetoothGatt.getService(RX_SERVICE_UUID);
+ showMessage("mBluetoothGatt null"+ mBluetoothGatt);
+ if (RxService == null) {
+ showMessage("Rx service not found!");
+ broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
+ return;
+ }
+ BluetoothGattCharacteristic RxChar = RxService.getCharacteristic(RX_CHAR_UUID);
+ if (RxChar == null) {
+ showMessage("Rx charateristic not found!");
+ broadcastUpdate(DEVICE_DOES_NOT_SUPPORT_UART);
+ return;
+ }
+ RxChar.setValue(value);
+ boolean status = mBluetoothGatt.writeCharacteristic(RxChar);
+
+ Log.d(TAG, "write TXchar - status=" + status);
+ }
+
+ private void showMessage(String msg) {
+ Log.e(TAG, msg);
+ }
+ /**
+ * Retrieves a list of supported GATT services on the connected device. This should be
+ * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
+ *
+ * @return A {@code List} of supported services.
+ */
+ public List getSupportedGattServices() {
+ if (mBluetoothGatt == null) return null;
+
+ return mBluetoothGatt.getServices();
+ }
+}
+
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..96a442e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-hdpi/nrfuart_hdpi_icon.png b/app/src/main/res/drawable-hdpi/nrfuart_hdpi_icon.png
new file mode 100755
index 0000000..785ace6
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/nrfuart_hdpi_icon.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..359047d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..71c6d76
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..4df1894
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/layout/device_element.xml b/app/src/main/res/layout/device_element.xml
new file mode 100755
index 0000000..cf0149d
--- /dev/null
+++ b/app/src/main/res/layout/device_element.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/device_list.xml b/app/src/main/res/layout/device_list.xml
new file mode 100755
index 0000000..13b5acc
--- /dev/null
+++ b/app/src/main/res/layout/device_list.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dis_values.xml b/app/src/main/res/layout/dis_values.xml
new file mode 100755
index 0000000..5250e14
--- /dev/null
+++ b/app/src/main/res/layout/dis_values.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
new file mode 100755
index 0000000..2343939
--- /dev/null
+++ b/app/src/main/res/layout/main.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/message_detail.xml b/app/src/main/res/layout/message_detail.xml
new file mode 100755
index 0000000..e415ec8
--- /dev/null
+++ b/app/src/main/res/layout/message_detail.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/app/src/main/res/layout/title_bar.xml b/app/src/main/res/layout/title_bar.xml
new file mode 100755
index 0000000..c509aa2
--- /dev/null
+++ b/app/src/main/res/layout/title_bar.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100755
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100755
index 0000000..47c8224
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100755
index 0000000..7488bde
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ Suit LEDs
+ Device:
+ <Select a device>
+ Select a device
+ Status
+ LinkLossAlert
+ ImmediateAlert
+ Scanning for devices...
+ No LE devices found.
+ Connected
+ Not connected
+ Ready
+ Disconnect
+ connect
+ unpair
+ pair
+ Alert level high
+ Alert level mid
+ Stop alerting
+ SystemId
+ ModelNumber
+ SerialNumber
+ FirmwareRevision
+ HardwareRevision
+ SoftwareRevision
+ ManufactureName
+ RegCertDataList
+ ManufactureId
+ Battery Level
+ OrganizationId
+ Txpower Level
+ No Update
+ ReadTxPower
+ HIGH Alert triggered!
+ LOW Alert triggered!
+ TxNoti
+ Read Rssi Value
+ D Read
+ Remove Bond
+ paired
+ Quit nRF UART?
+ Do you want to quit this Application?
+ YES
+ NO
+ Bluetooth Low Energy not supported
+ Scan
+ Cancel
+
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100755
index 0000000..50132c4
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+ #FF000000
+ #FFF2F2F2
+
+
+
+
diff --git a/app/src/test/java/us/keiran/suitleds/ExampleUnitTest.java b/app/src/test/java/us/keiran/suitleds/ExampleUnitTest.java
new file mode 100755
index 0000000..caf55a7
--- /dev/null
+++ b/app/src/test/java/us/keiran/suitleds/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package us.keiran.suitleds;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100755
index 0000000..f208c52
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.3.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100755
index 0000000..9f85f38
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100755
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 0000000..2964917
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Mar 11 21:30:21 EDT 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100755
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100755
index 0000000..d3db109
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'