├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── apk_01.apk ├── app-release.apk ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── thejeshgn │ │ └── com │ │ └── bleuartperipheral │ │ ├── AboutActivity.java │ │ ├── MainActivity.java │ │ ├── UARTProfile.java │ │ ├── data │ │ ├── DataManager.java │ │ └── Util.java │ │ └── log │ │ └── LogProvider.java │ └── res │ ├── layout │ ├── activity_about.xml │ └── activity_main.xml │ ├── menu │ └── main_tool_bar.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Thejesh GN 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BleUARTPeripheral 2 | Android App for running UART GATT Server (Peripheral) on phone 3 | 4 | 5 | #Logo 6 | Based on [Serial Port image by Dalpat Prajapati](https://thenounproject.com/DalpatPrajapati/) 7 | 8 | #Install 9 | You can install from [Playstore](https://play.google.com/store/apps/details?id=thejeshgn.com.bleuartperipheral) or from Release on GH. 10 | 11 | 12 | #TODOs 13 | - Give a start and stop button for the GATT server 14 | - Add an about page with credits 15 | - NRF Loggger display 16 | - Ability to recieive and show Sensor Data Sent using [Adafruit UART Controller](https://learn.adafruit.com/bluefruit-le-connect-for-ios/controller) 17 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/apk_01.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/apk_01.apk -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/app-release.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | defaultConfig { 7 | applicationId "thejeshgn.com.bleuartperipheral" 8 | minSdkVersion 21 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.1' 28 | compile 'no.nordicsemi.android:log:2.0.0' 29 | compile 'com.android.support:design:24.2.1' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/thej/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v4.app.NavUtils; 8 | import android.support.v4.app.TaskStackBuilder; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.text.Html; 13 | import android.text.method.LinkMovementMethod; 14 | import android.text.util.Linkify; 15 | import android.view.MenuItem; 16 | import android.view.View; 17 | import android.widget.TextView; 18 | 19 | public class AboutActivity extends AppCompatActivity { 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_about); 25 | setTitle("About"); 26 | ActionBar actionBar = getSupportActionBar(); 27 | actionBar.setDisplayHomeAsUpEnabled(true); 28 | 29 | 30 | 31 | TextView text = new TextView(this); 32 | text.setPadding(20, 20, 20, 10); 33 | text.setMovementMethod(LinkMovementMethod.getInstance()); 34 | setContentView(text); 35 | StringBuilder content =new StringBuilder("

BleUARTPeripheral is an Android app that simulates the UART Peripheral on phone. This is for developers to test their client app.

"); 36 | content.append("

Its a UART simple server, once connected it responds to commands like whoami or date etc. If it can’t figure then it just echos whatever client has sent.

"); 37 | content.append("

Its Free and Open Source App. You can use the app as is or use the code in your project. More details on GitHub

"); 38 | content.append("

Logo is based on Serial Port image by Dalpat Prajapati.

"); 39 | content.append("

Thejesh GN

"); 40 | text.setText(Html.fromHtml(content.toString())); 41 | } 42 | 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | switch (item.getItemId()) { 47 | case android.R.id.home: 48 | // This is called when the Home (Up) button is pressed in the action bar. 49 | // Create a simple intent that starts the hierarchical parent activity and 50 | // use NavUtils in the Support Package to ensure proper handling of Up. 51 | Intent upIntent = new Intent(this, MainActivity.class); 52 | if (NavUtils.shouldUpRecreateTask(this, upIntent)) { 53 | // This activity is not part of the application's task, so create a new task 54 | // with a synthesized back stack. 55 | TaskStackBuilder.from(this) 56 | // If there are ancestor activities, they should be added here. 57 | .addNextIntent(upIntent) 58 | .startActivities(); 59 | finish(); 60 | } else { 61 | // This activity is part of the application's task, so simply 62 | // navigate up to the hierarchical parent activity. 63 | NavUtils.navigateUpTo(this, upIntent); 64 | } 65 | return true; 66 | } 67 | return super.onOptionsItemSelected(item); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/MainActivity.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.ParcelUuid; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | 9 | import java.text.DateFormat; 10 | import java.text.SimpleDateFormat; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | import android.bluetooth.BluetoothAdapter; 16 | import android.bluetooth.BluetoothDevice; 17 | import android.bluetooth.BluetoothGatt; 18 | import android.bluetooth.BluetoothGattCharacteristic; 19 | import android.bluetooth.BluetoothGattDescriptor; 20 | import android.bluetooth.BluetoothGattServer; 21 | import android.bluetooth.BluetoothGattServerCallback; 22 | import android.bluetooth.BluetoothGattService; 23 | import android.bluetooth.BluetoothManager; 24 | import android.bluetooth.BluetoothProfile; 25 | import android.bluetooth.le.AdvertiseCallback; 26 | import android.bluetooth.le.AdvertiseData; 27 | import android.bluetooth.le.AdvertiseSettings; 28 | import android.bluetooth.le.BluetoothLeAdvertiser; 29 | import android.content.Context; 30 | import android.content.pm.PackageManager; 31 | import android.os.Handler; 32 | import android.util.Log; 33 | import android.view.Menu; 34 | import android.view.MenuItem; 35 | import android.widget.ArrayAdapter; 36 | import android.widget.ListView; 37 | import android.widget.Toast; 38 | 39 | import no.nordicsemi.android.log.ILogSession; 40 | import no.nordicsemi.android.log.LogContract; 41 | import no.nordicsemi.android.log.Logger; 42 | import thejeshgn.com.bleuartperipheral.data.DataManager; 43 | import thejeshgn.com.bleuartperipheral.data.Util; 44 | 45 | /** 46 | * It has everything that is needed by GattServer to respond 47 | * MIT Licnese 48 | * 2016 - Thejesh GN - https://hejeshgn.com 49 | */ 50 | 51 | 52 | public class MainActivity extends AppCompatActivity { 53 | private static final String TAG = "MainActivity"; 54 | private ILogSession mLogSession; 55 | private DataManager dataManager = DataManager.getInstance(); 56 | private BluetoothManager mBluetoothManager; 57 | private BluetoothAdapter mBluetoothAdapter; 58 | private BluetoothLeAdvertiser mBluetoothLeAdvertiser; 59 | private BluetoothGattServer mGattServer; 60 | 61 | private ArrayList mConnectedDevices; 62 | private ArrayAdapter mConnectedDevicesAdapter; 63 | 64 | @Override 65 | protected void onCreate(Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | mLogSession = Logger.newSession(this, "Hello", "BleUARTPeripheral"); 68 | 69 | setContentView(R.layout.activity_main); 70 | 71 | ListView list = new ListView(this); 72 | setContentView(list); 73 | 74 | mConnectedDevices = new ArrayList(); 75 | mConnectedDevicesAdapter = new ArrayAdapter(this, 76 | android.R.layout.simple_list_item_1, mConnectedDevices); 77 | list.setAdapter(mConnectedDevicesAdapter); 78 | 79 | mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE); 80 | mBluetoothAdapter = mBluetoothManager.getAdapter(); 81 | Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "In on create"); 82 | 83 | } 84 | 85 | @Override 86 | public boolean onCreateOptionsMenu(Menu menu) { 87 | // Inflate the menu; this adds items to the action bar if it is present. 88 | getMenuInflater().inflate(R.menu.main_tool_bar, menu); 89 | return true; 90 | } 91 | 92 | 93 | @Override 94 | public boolean onOptionsItemSelected(MenuItem item) { 95 | Toast.makeText(this, "Inside menu item.", Toast.LENGTH_SHORT).show(); 96 | Logger.log(mLogSession, LogContract.Log.Level.INFO, "Inside onOptionsItemSelected"); 97 | switch (item.getItemId()) { 98 | case R.id.action_about: 99 | Intent myIntent = new Intent(MainActivity.this, AboutActivity.class); 100 | startActivity(myIntent); 101 | return true; 102 | 103 | case R.id.action_log: 104 | return true; 105 | 106 | case R.id.action_start_stop: 107 | // User chose the "Favorite" action, mark the current item 108 | // as a favorite... 109 | return true; 110 | 111 | default: 112 | // If we got here, the user's action was not recognized. 113 | // Invoke the superclass to handle it. 114 | return super.onOptionsItemSelected(item); 115 | 116 | } 117 | } 118 | 119 | protected void onResume() { 120 | super.onResume(); 121 | /* 122 | * Make sure bluettoth is enabled 123 | */ 124 | if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { 125 | //Bluetooth is disabled 126 | Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Bluetooth is disabled. Request enable"); 127 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 128 | startActivity(enableBtIntent); 129 | finish(); 130 | return; 131 | } 132 | 133 | /* 134 | * Check for Bluetooth LE Support 135 | */ 136 | if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 137 | Toast.makeText(this, "No LE Support.", Toast.LENGTH_SHORT).show(); 138 | finish(); 139 | return; 140 | } 141 | 142 | /* 143 | * Check for advertising support. 144 | */ 145 | if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) { 146 | Toast.makeText(this, "No Advertising Support.", Toast.LENGTH_SHORT).show(); 147 | finish(); 148 | return; 149 | } 150 | Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Get Advertiser"); 151 | mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); 152 | Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Open GattServer"); 153 | mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback); 154 | 155 | // If everything is okay then start 156 | initServer(); 157 | startAdvertising(); 158 | } 159 | 160 | /* 161 | * Callback handles events from the framework describing 162 | * if we were successful in starting the advertisement requests. 163 | */ 164 | private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() { 165 | @Override 166 | public void onStartSuccess(AdvertiseSettings settingsInEffect) { 167 | Log.i(TAG, "Peripheral Advertise Started."); 168 | postStatusMessage("GATT Server Ready"); 169 | } 170 | 171 | @Override 172 | public void onStartFailure(int errorCode) { 173 | Log.w(TAG, "Peripheral Advertise Failed: " + errorCode); 174 | postStatusMessage("GATT Server Error " + errorCode); 175 | } 176 | }; 177 | 178 | private Handler mHandler = new Handler(); 179 | 180 | private void postStatusMessage(final String message) { 181 | mHandler.post(new Runnable() { 182 | @Override 183 | public void run() { 184 | setTitle(message); 185 | } 186 | }); 187 | } 188 | 189 | /* 190 | * Create the GATT server instance, attaching all services and 191 | * characteristics that should be exposed 192 | */ 193 | private void initServer() { 194 | BluetoothGattService UART_SERVICE = new BluetoothGattService(UARTProfile.UART_SERVICE, 195 | BluetoothGattService.SERVICE_TYPE_PRIMARY); 196 | 197 | BluetoothGattCharacteristic TX_READ_CHAR = 198 | new BluetoothGattCharacteristic(UARTProfile.TX_READ_CHAR, 199 | //Read-only characteristic, supports notifications 200 | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, 201 | BluetoothGattCharacteristic.PERMISSION_READ); 202 | 203 | //Descriptor for read notifications 204 | BluetoothGattDescriptor TX_READ_CHAR_DESC = new BluetoothGattDescriptor(UARTProfile.TX_READ_CHAR_DESC, UARTProfile.DESCRIPTOR_PERMISSION); 205 | TX_READ_CHAR.addDescriptor(TX_READ_CHAR_DESC); 206 | 207 | 208 | BluetoothGattCharacteristic RX_WRITE_CHAR = 209 | new BluetoothGattCharacteristic(UARTProfile.RX_WRITE_CHAR, 210 | //write permissions 211 | BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE); 212 | 213 | 214 | UART_SERVICE.addCharacteristic(TX_READ_CHAR); 215 | UART_SERVICE.addCharacteristic(RX_WRITE_CHAR); 216 | 217 | mGattServer.addService(UART_SERVICE); 218 | } 219 | 220 | 221 | /* 222 | * Initialize the advertiser 223 | */ 224 | private void startAdvertising() { 225 | if (mBluetoothLeAdvertiser == null) return; 226 | 227 | AdvertiseSettings settings = new AdvertiseSettings.Builder() 228 | .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) 229 | .setConnectable(true) 230 | .setTimeout(0) 231 | .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) 232 | .build(); 233 | 234 | AdvertiseData data = new AdvertiseData.Builder() 235 | .setIncludeDeviceName(true) 236 | .addServiceUuid(new ParcelUuid(UARTProfile.UART_SERVICE)) 237 | .build(); 238 | 239 | mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback); 240 | } 241 | 242 | 243 | private void postDeviceChange(final BluetoothDevice device, final boolean toAdd) { 244 | mHandler.post(new Runnable() { 245 | @Override 246 | public void run() { 247 | //This will add the item to our list and update the adapter at the same time. 248 | if (toAdd) { 249 | if (mConnectedDevicesAdapter.getPosition(device) < 0) { 250 | mConnectedDevicesAdapter.add(device); 251 | } 252 | 253 | } else { 254 | mConnectedDevicesAdapter.remove(device); 255 | } 256 | 257 | } 258 | }); 259 | } 260 | 261 | 262 | /* 263 | * Terminate the server and any running callbacks 264 | */ 265 | private void shutdownServer() { 266 | //mHandler.removeCallbacks(mNotifyRunnable); 267 | 268 | if (mGattServer == null) return; 269 | 270 | mGattServer.close(); 271 | } 272 | 273 | // private Runnable mNotifyRunnable = new Runnable() { 274 | // @Override 275 | // public void run() { 276 | // mHandler.postDelayed(this, 2000); 277 | // } 278 | // }; 279 | 280 | 281 | /* Callback handles all incoming requests from GATT clients. 282 | * From connections to read/write requests. 283 | */ 284 | private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() { 285 | @Override 286 | public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { 287 | super.onConnectionStateChange(device, status, newState); 288 | Log.i(TAG, "onConnectionStateChange " 289 | + UARTProfile.getStatusDescription(status) + " " 290 | + UARTProfile.getStateDescription(newState)); 291 | 292 | if (newState == BluetoothProfile.STATE_CONNECTED) { 293 | postDeviceChange(device, true); 294 | 295 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 296 | postDeviceChange(device, false); 297 | } 298 | } 299 | 300 | @Override 301 | public void onServiceAdded(int status, BluetoothGattService service) { 302 | Log.d("Start", "Our gatt server service was added."); 303 | super.onServiceAdded(status, service); 304 | } 305 | 306 | 307 | @Override 308 | public void onCharacteristicReadRequest(BluetoothDevice device, 309 | int requestId, 310 | int offset, 311 | BluetoothGattCharacteristic characteristic) { 312 | super.onCharacteristicReadRequest(device, requestId, offset, characteristic); 313 | Log.d(TAG, "READ called onCharacteristicReadRequest " + characteristic.getUuid().toString()); 314 | 315 | if (UARTProfile.TX_READ_CHAR.equals(characteristic.getUuid())) { 316 | mGattServer.sendResponse(device, 317 | requestId, 318 | BluetoothGatt.GATT_SUCCESS, 319 | 0, 320 | storage); 321 | } 322 | 323 | } 324 | 325 | 326 | @Override 327 | public void onCharacteristicWriteRequest(BluetoothDevice device, 328 | int requestId, 329 | BluetoothGattCharacteristic characteristic, 330 | boolean preparedWrite, 331 | boolean responseNeeded, 332 | int offset, 333 | byte[] value) { 334 | super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); 335 | Log.i(TAG, "onCharacteristicWriteRequest " + characteristic.getUuid().toString()); 336 | 337 | if (UARTProfile.RX_WRITE_CHAR.equals(characteristic.getUuid())) { 338 | 339 | //IMP: Copy the received value to storage 340 | if (responseNeeded) { 341 | mGattServer.sendResponse(device, 342 | requestId, 343 | BluetoothGatt.GATT_SUCCESS, 344 | 0, 345 | value); 346 | Log.d(TAG, "Received data on " + characteristic.getUuid().toString()); 347 | Log.d(TAG, "Received data" + bytesToHex(value)); 348 | 349 | } 350 | 351 | dataManager.addPacket(value); 352 | if (dataManager.isMessageComplete()){ 353 | storage = dataManager.getTheCompleteMesssage(); 354 | dataManager.clear(); 355 | //IMP: Respond 356 | sendOurResponse(); 357 | 358 | } 359 | 360 | 361 | 362 | mHandler.post(new Runnable() { 363 | @Override 364 | public void run() { 365 | Toast.makeText(MainActivity.this, "We received data", Toast.LENGTH_SHORT).show(); 366 | } 367 | }); 368 | 369 | 370 | } 371 | } 372 | 373 | 374 | @Override 375 | public void onNotificationSent(BluetoothDevice device, int status) { 376 | Log.d("GattServer", "onNotificationSent"); 377 | super.onNotificationSent(device, status); 378 | } 379 | 380 | 381 | @Override 382 | public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { 383 | Log.d("HELLO", "Our gatt server descriptor was read."); 384 | super.onDescriptorReadRequest(device, requestId, offset, descriptor); 385 | Log.d("DONE", "Our gatt server descriptor was read."); 386 | } 387 | 388 | @Override 389 | public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { 390 | Log.d("HELLO", "Our gatt server descriptor was written."); 391 | super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); 392 | Log.d("DONE", "Our gatt server descriptor was written."); 393 | 394 | //NOTE: Its important to send response. It expects response else it will disconnect 395 | if (responseNeeded) { 396 | mGattServer.sendResponse(device, 397 | requestId, 398 | BluetoothGatt.GATT_SUCCESS, 399 | 0, 400 | value); 401 | 402 | } 403 | 404 | } 405 | 406 | //end of gatt server 407 | }; 408 | 409 | 410 | //Send notification to all the devices once you write 411 | private void sendOurResponse() { 412 | for (BluetoothDevice device : mConnectedDevices) { 413 | BluetoothGattCharacteristic readCharacteristic = mGattServer.getService(UARTProfile.UART_SERVICE) 414 | .getCharacteristic(UARTProfile.TX_READ_CHAR); 415 | 416 | byte[] notify_msg = storage; 417 | String hexStorage = bytesToHex(storage); 418 | Log.d(TAG, "received string = " + bytesToHex(storage)); 419 | 420 | 421 | if (hexStorage.equals("77686F616D69")) { 422 | 423 | notify_msg = "I am echo an machine".getBytes(); 424 | 425 | } else if (bytesToHex(storage).equals("64617465")) { 426 | DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 427 | Date date = new Date(); 428 | notify_msg = dateFormat.format(date).getBytes(); 429 | 430 | } else { 431 | //TODO: Do nothing send what you received. Basically echo 432 | } 433 | 434 | List messages = Util.createPacketsToSend(notify_msg); 435 | for(int i =0; i < messages.size(); i++){ 436 | byte[] message =(byte[]) messages.get(i); 437 | readCharacteristic.setValue(message); 438 | Log.d(TAG, "Sending Notifications" + message); 439 | boolean is_notified = mGattServer.notifyCharacteristicChanged(device, readCharacteristic, false); 440 | Log.d(TAG, "Notifications =" + is_notified); 441 | } 442 | } 443 | } 444 | 445 | 446 | private byte[] storage = hexStringToByteArray("1111"); 447 | 448 | final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 449 | 450 | //Helper function converts byte array to hex string 451 | //for priting 452 | public static String bytesToHex(byte[] bytes) { 453 | char[] hexChars = new char[bytes.length * 2]; 454 | for (int j = 0; j < bytes.length; j++) { 455 | int v = bytes[j] & 0xFF; 456 | hexChars[j * 2] = hexArray[v >>> 4]; 457 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 458 | } 459 | return new String(hexChars); 460 | } 461 | 462 | 463 | //Helper function converts hex string into 464 | //byte array 465 | public static byte[] hexStringToByteArray(String s) { 466 | int len = s.length(); 467 | byte[] data = new byte[len / 2]; 468 | for (int i = 0; i < len; i += 2) { 469 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 470 | + Character.digit(s.charAt(i + 1), 16)); 471 | } 472 | return data; 473 | } 474 | 475 | 476 | } 477 | -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/UARTProfile.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral; 2 | 3 | 4 | import android.bluetooth.BluetoothGatt; 5 | import android.bluetooth.BluetoothGattDescriptor; 6 | import android.bluetooth.BluetoothProfile; 7 | 8 | import java.nio.ByteBuffer; 9 | import java.nio.ByteOrder; 10 | import java.util.UUID; 11 | 12 | /** 13 | * UART Profile details 14 | * MIT Licnese 15 | * 2016 - Thejesh GN - https://hejeshgn.com 16 | */ 17 | 18 | 19 | public class UARTProfile { 20 | //Service UUID to expose our UART characteristics 21 | public static UUID UART_SERVICE = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); 22 | 23 | //RX, Write characteristic 24 | public static UUID RX_WRITE_CHAR = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); 25 | 26 | //TX READ Notify 27 | public static UUID TX_READ_CHAR = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); 28 | public static UUID TX_READ_CHAR_DESC = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 29 | public final static int DESCRIPTOR_PERMISSION = BluetoothGattDescriptor.PERMISSION_WRITE; 30 | 31 | public static String getStateDescription(int state) { 32 | switch (state) { 33 | case BluetoothProfile.STATE_CONNECTED: 34 | return "Connected"; 35 | case BluetoothProfile.STATE_CONNECTING: 36 | return "Connecting"; 37 | case BluetoothProfile.STATE_DISCONNECTED: 38 | return "Disconnected"; 39 | case BluetoothProfile.STATE_DISCONNECTING: 40 | return "Disconnecting"; 41 | default: 42 | return "Unknown State " + state; 43 | } 44 | } 45 | 46 | 47 | public static String getStatusDescription(int status) { 48 | switch (status) { 49 | case BluetoothGatt.GATT_SUCCESS: 50 | return "SUCCESS"; 51 | default: 52 | return "Unknown Status " + status; 53 | } 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/data/DataManager.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral.data; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * Created by thej on 15/12/16. 9 | */ 10 | 11 | 12 | public class DataManager { 13 | private final static String TAG = DataManager.class.getSimpleName(); 14 | private static DataManager instance; 15 | private static List allPackets = new ArrayList(); 16 | private static boolean final_packet_received = false; 17 | 18 | private DataManager(){ 19 | 20 | } 21 | 22 | public static DataManager getInstance() { 23 | if (null == instance) { 24 | instance = new DataManager(); 25 | } 26 | return instance; 27 | } 28 | 29 | public static void addPacket(byte[] packet){ 30 | allPackets.add(packet); 31 | //if its last packet by checking if the last byte is \n 32 | if (packet[packet.length-2] == Util.packet_indicator_last[0] && packet[packet.length-1] == Util.packet_indicator_last[1]){ 33 | final_packet_received = true; 34 | } 35 | } 36 | 37 | public static boolean isMessageComplete(){ 38 | return final_packet_received; 39 | } 40 | 41 | 42 | 43 | public static byte[] getTheCompleteMesssage(){ 44 | byte[] final_message={}; 45 | for (int time = 0; time < allPackets.size(); time++) { 46 | byte[] packet = (byte[])allPackets.get(time); 47 | final_message = Util.appendData(final_message,packet); 48 | } 49 | return final_message; 50 | } 51 | 52 | public static void clear(){ 53 | allPackets = new ArrayList(); 54 | final_packet_received = false; 55 | } 56 | 57 | 58 | 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/data/Util.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral.data; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by thej on 15/12/16. 11 | */ 12 | 13 | public class Util { 14 | public static byte[] packet_indicator_last ={0x5c, 0x6e}; 15 | public static int packet_size = 20; 16 | 17 | public static byte[] appendData(byte[] firstObject,byte[] secondObject){ 18 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); 19 | try { 20 | if (firstObject!=null && firstObject.length!=0) 21 | outputStream.write(firstObject); 22 | if (secondObject!=null && secondObject.length!=0) 23 | outputStream.write(secondObject); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | return outputStream.toByteArray(); 28 | } 29 | 30 | 31 | public static List createPacketsToSend(byte[] message){ 32 | List allPackets = new ArrayList(); 33 | message = appendData(message, packet_indicator_last); 34 | 35 | int times = message.length/packet_size; 36 | 37 | 38 | for (short time = 0; time <= times; time++) { 39 | byte[] packet = Arrays.copyOfRange(message, time*packet_size, (time+1)*packet_size); 40 | allPackets.add(packet); 41 | } 42 | return allPackets; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/thejeshgn/com/bleuartperipheral/log/LogProvider.java: -------------------------------------------------------------------------------- 1 | package thejeshgn.com.bleuartperipheral.log; 2 | 3 | import no.nordicsemi.android.log.localprovider.LocalLogContentProvider; 4 | import android.net.Uri; 5 | 6 | /** 7 | * Created by thej on 11/12/16. 8 | */ 9 | 10 | public class LogProvider extends LocalLogContentProvider { 11 | /** The authority for the contacts provider. */ 12 | public static final String AUTHORITY = "thejeshgn.com.bleuartperipheral"; 13 | /** A content:// style uri to the authority for the log provider. */ 14 | public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); 15 | 16 | @Override 17 | protected Uri getAuthorityUri() { 18 | return AUTHORITY_URI; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_tool_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejeshgn/BleUARTPeripheral/e1f89708368c382207f35b834070b90dcb4db8f0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BleUARTPeripheral 3 | AboutActivity 4 | StartActivity 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 |