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 @@ + + + + + + + + + + + + + + + + +