Skip to content

Commit

Permalink
Added library
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnvanwelie committed Mar 2, 2019
1 parent 11ea17e commit b6a101f
Show file tree
Hide file tree
Showing 46 changed files with 4,266 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
29 changes: 29 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.welie.blessedexample"
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(include: ['*.jar'], dir: 'libs')
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'
implementation project(':blessed')
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.welie.blessedexample;

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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("com.welie.blessedexample", appContext.getPackageName());
}
}
25 changes: 25 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.welie.blessedexample">

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.welie.blessedexample;

import com.welie.blessed.BluetoothBytesParser;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import static android.bluetooth.BluetoothGattCharacteristic.FORMAT_SFLOAT;
import static android.bluetooth.BluetoothGattCharacteristic.FORMAT_UINT8;

public class BloodPressureMeasurement implements Serializable {

public Integer userID;
public Float systolic;
public Float diastolic;
public Float meanArterialPressure;
public Date timestamp;
public boolean isMMHG;
public Float pulseRate;

public BloodPressureMeasurement(byte[] value) {
BluetoothBytesParser parser = new BluetoothBytesParser(value);

// Parse the flags
int flags = parser.getIntValue(FORMAT_UINT8);
isMMHG = !((flags & 0x01) > 0);
boolean timestampPresent = (flags & 0x02) > 0;
boolean pulseRatePresent = (flags & 0x04) > 0;
boolean userIdPresent = (flags & 0x08) > 0;
boolean measurementStatusPresent = (flags & 0x10) > 0;

// Get systolic, diastolic and mean arterial pressure
systolic = parser.getFloatValue(FORMAT_SFLOAT);
diastolic = parser.getFloatValue(FORMAT_SFLOAT);
meanArterialPressure = parser.getFloatValue(FORMAT_SFLOAT);

// Read timestamp
if (timestampPresent) {
timestamp = parser.getDateTime();
} else {
timestamp = Calendar.getInstance().getTime();
}

// Read pulse rate
if (pulseRatePresent) {
pulseRate = parser.getFloatValue(FORMAT_SFLOAT);
}

// Read userId
if (userIdPresent) {
userID = parser.getIntValue(FORMAT_UINT8);
}
}

@Override
public String toString() {
return String.format(Locale.ENGLISH,"%.0f/%.0f %s, MAP %.0f, %.0f bpm, user %d at (%s)", systolic, diastolic, isMMHG ? "mmHg" : "kPa", meanArterialPressure, pulseRate, userID, timestamp);
}
}
208 changes: 208 additions & 0 deletions app/src/main/java/com/welie/blessedexample/BluetoothHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package com.welie.blessedexample;

import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;

import com.welie.blessed.BluetoothBytesParser;
import com.welie.blessed.BluetoothCentral;
import com.welie.blessed.BluetoothCentralCallback;
import com.welie.blessed.BluetoothPeripheral;
import com.welie.blessed.BluetoothPeripheralCallback;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
import static com.welie.blessed.BluetoothBytesParser.bytes2String;
import static com.welie.blessed.BluetoothPeripheral.GATT_SUCCESS;
import static java.lang.Math.abs;

public class BluetoothHandler {
private final String TAG = BluetoothHandler.class.getSimpleName();

// UUIDs for the Blood Pressure service (BLP)
private static final UUID BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb");
private static final UUID BLOOD_PRESSURE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb");

// UUIDs for the Device Information service (DIS)
private static final UUID DIS_SERVICE_UUID = UUID.fromString("0000180A-0000-1000-8000-00805f9b34fb");
private static final UUID MANUFACTURER_NAME_CHARACTERISTIC_UUID = UUID.fromString("00002A29-0000-1000-8000-00805f9b34fb");
private static final UUID MODEL_NUMBER_CHARACTERISTIC_UUID = UUID.fromString("00002A24-0000-1000-8000-00805f9b34fb");

// UUIDs for the Current Time service (CTS)
private static final UUID CTS_SERVICE_UUID = UUID.fromString("00001805-0000-1000-8000-00805f9b34fb");
private static final UUID CURRENT_TIME_CHARACTERISTIC_UUID = UUID.fromString("00002A2B-0000-1000-8000-00805f9b34fb");

// UUIDs for the Battery Service (BAS)
private static final UUID BTS_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb");
private static final UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb");

private static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

// Local variables
private BluetoothCentral central;
private static BluetoothHandler instance = null;
private Context context;
private Handler handler = new Handler();
private int currentTimeCounter = 0;

// Callback for peripherals
private final BluetoothPeripheralCallback peripheralCallback = new BluetoothPeripheralCallback() {
@Override
public void onServicesDiscovered(BluetoothPeripheral peripheral) {

// Read manufacturer and model number from the Device Information Service
if(peripheral.getService(DIS_SERVICE_UUID) != null) {
peripheral.readCharacteristic(peripheral.getCharacteristic(DIS_SERVICE_UUID, MANUFACTURER_NAME_CHARACTERISTIC_UUID));
peripheral.readCharacteristic(peripheral.getCharacteristic(DIS_SERVICE_UUID, MODEL_NUMBER_CHARACTERISTIC_UUID));
}

// Turn on notifications for Current Time Service
if(peripheral.getService(CTS_SERVICE_UUID) != null) {
BluetoothGattCharacteristic currentTimeCharacteristic = peripheral.getCharacteristic(CTS_SERVICE_UUID, CURRENT_TIME_CHARACTERISTIC_UUID);
peripheral.setNotify(currentTimeCharacteristic, true);

// If it has the write property we write the current time
if((currentTimeCharacteristic.getProperties() & PROPERTY_WRITE) > 0) {
// Write the current time unless it is an Omron device
if(!(peripheral.getName().contains("BLEsmart_"))) {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setCurrentTime(Calendar.getInstance());
peripheral.writeCharacteristic(currentTimeCharacteristic, parser.getValue(), WRITE_TYPE_DEFAULT);
}
}
}

// Turn on notifications for Battery Service
if(peripheral.getService(BTS_SERVICE_UUID) != null) {
peripheral.setNotify(peripheral.getCharacteristic(BTS_SERVICE_UUID, BATTERY_LEVEL_CHARACTERISTIC_UUID), true);
}

// Turn on notifications for Blood Pressure Service
if(peripheral.getService(BLP_SERVICE_UUID) != null) {
peripheral.setNotify(peripheral.getCharacteristic(BLP_SERVICE_UUID, BLOOD_PRESSURE_MEASUREMENT_CHARACTERISTIC_UUID), true);
}
}

@Override
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, int status) {
if( status == GATT_SUCCESS) {
if(peripheral.isNotifying(characteristic)) {
Log.i(TAG, String.format("SUCCESS: Notify set to 'on' for %s", characteristic.getUuid()));
} else {
Log.i(TAG, String.format("SUCCESS: Notify set to 'off' for %s", characteristic.getUuid()));
}
} else {
Log.e(TAG, String.format("ERROR: Changing notification state failed for %s", characteristic.getUuid()));
}
}

@Override
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, int status) {
if( status == GATT_SUCCESS) {
Log.i(TAG, String.format("SUCCESS: Writing <%s> to <%s>", bytes2String(value), characteristic.getUuid().toString()));
} else {
Log.i(TAG, String.format("ERROR: Failed writing <%s> to <%s>", bytes2String(value), characteristic.getUuid().toString()));
}
}

@Override
public void onCharacteristicUpdate(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
BluetoothBytesParser parser = new BluetoothBytesParser(value);

if (characteristicUUID.equals(BLOOD_PRESSURE_MEASUREMENT_CHARACTERISTIC_UUID)) {
BloodPressureMeasurement measurement = new BloodPressureMeasurement(value);
Intent intent = new Intent("BluetoothMeasurement");
intent.putExtra("BloodPressure", measurement);
context.sendBroadcast(intent);
Log.d(TAG, String.format("%s", measurement));
}
else if(characteristicUUID.equals(CURRENT_TIME_CHARACTERISTIC_UUID)) {
Date currentTime = parser.getDateTime();
Log.i(TAG, String.format("Received device time: %s", currentTime));

// Deal with Omron devices where we can only write currentTime under specific conditions
if(peripheral.getName().contains("BLEsmart_")) {
boolean isNotifying = peripheral.isNotifying(peripheral.getCharacteristic(BLP_SERVICE_UUID, BLOOD_PRESSURE_MEASUREMENT_CHARACTERISTIC_UUID));
if(isNotifying) currentTimeCounter++;

// We can set device time for Omron devices only if it is the first notification and currentTime is more than 10 min from now
long interval = abs(Calendar.getInstance().getTimeInMillis() - currentTime.getTime());
if (currentTimeCounter == 1 && interval > 10*60*1000) {
parser.setCurrentTime(Calendar.getInstance());
peripheral.writeCharacteristic(characteristic, parser.getValue(), WRITE_TYPE_DEFAULT);
}
}
}
else if(characteristicUUID.equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) {
int batteryLevel = parser.getIntValue(FORMAT_UINT8);
Log.i(TAG, String.format("Received battery level %d%%", batteryLevel));
}
else if(characteristicUUID.equals(MANUFACTURER_NAME_CHARACTERISTIC_UUID)) {
String manufacturer = parser.getStringValue(0);
Log.i(TAG, String.format("Received manufacturer: %s", manufacturer));
}
else if(characteristicUUID.equals(MODEL_NUMBER_CHARACTERISTIC_UUID)) {
String modelNumber = parser.getStringValue(0);
Log.i(TAG, String.format("Received modelnumber: %s", modelNumber));
}
}
};

// Callback for central
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {

@Override
public void onConnectedPeripheral(BluetoothPeripheral peripheral) {
Log.i(TAG, String.format("connected to '%s'", peripheral.getName()));
}

@Override
public void onConnectionFailed(BluetoothPeripheral peripheral, final int status) {
Log.e(TAG, String.format("connection '%s' failed with status %d", peripheral.getName(), status ));
}

@Override
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, final int status) {
Log.i(TAG, String.format("disconnected '%s' with status %d", peripheral.getName(), status));

// Reconnect to this device when it becomes available again
handler.postDelayed(new Runnable() {
@Override
public void run() {
central.autoConnectPeripheral(peripheral, peripheralCallback);
}
}, 5000);
}

@Override
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
Log.i(TAG, String.format("Found peripheral '%s'", peripheral.getName()));
central.stopScan();
central.connectPeripheral(peripheral, peripheralCallback);
}
};

public static synchronized BluetoothHandler getInstance(Context context) {
if (instance == null) {
instance = new BluetoothHandler(context.getApplicationContext());
}
return instance;
}

private BluetoothHandler(Context context) {
this.context = context;
central = new BluetoothCentral(context, bluetoothCentralCallback, new Handler());

// Scan for peripherals with a certain service UUID
central.scanForPeripheralsWithServices(new UUID[]{BLP_SERVICE_UUID});
}
}
Loading

0 comments on commit b6a101f

Please sign in to comment.