-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
11ea17e
commit b6a101f
Showing
46 changed files
with
4,266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
26 changes: 26 additions & 0 deletions
26
app/src/androidTest/java/com/welie/blessedexample/ExampleInstrumentedTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
61 changes: 61 additions & 0 deletions
61
app/src/main/java/com/welie/blessedexample/BloodPressureMeasurement.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
208
app/src/main/java/com/welie/blessedexample/BluetoothHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); | ||
} | ||
} |
Oops, something went wrong.