Skip to content

Commit

Permalink
Merge branch 'NightscoutFoundation:master' into Navid_2023_05_26
Browse files Browse the repository at this point in the history
  • Loading branch information
Navid200 authored Nov 15, 2023
2 parents 6370323 + 3e86af7 commit 39f41d5
Show file tree
Hide file tree
Showing 73 changed files with 2,304 additions and 397 deletions.
57 changes: 57 additions & 0 deletions Documentation/technical/Incoming_Glucose_Broadcast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

# Integration with xDrip+ via Broadcast Intent

## Overview
This documentation provides guidance on how to send a broadcast intent from a third-party Android application to insert a glucose sensor record into the xDrip application. There are a few different broadcast receivers but this documents the Nightscout Emulation receiver just for glucose records.

## Sending a Broadcast Intent
To insert a glucose sensor record into xDrip, construct and send a broadcast intent with specific parameters as described below.

### Intent Action
Use the following intent action when constructing the intent:
```java
Intent intent = new Intent("com.eveningoutpost.dexdrip.NS_EMULATOR");
```

### Intent Package
To ensure the intent is received only by xDrip and to be allowed on Android 8+, specify the package name:
```java
intent.setPackage("com.eveningoutpost.dexdrip");
```

### Extra Parameters
Add extra parameters to the intent for the data collection and JSON payload containing the sensor readings.

- Collection: Indicate the type of data collection using the key `"collection"` with the value `"entries"`.
- Data: Provide the glucose sensor readings in a JSON array format with the key `"data"`.

### JSON Payload Structure
Construct the JSON payload with the glucose sensor readings as follows:
- Each reading should be a JSON object with the following attributes:
- `"type"`: Set to `"sgv"` for sensor glucose value record.
- `"date"`: The timestamp of the reading in milliseconds since epoch.
- `"sgv"`: The glucose value in mg/dL.
- `"direction"`: The rate of change of the glucose value, represented by a string such as `"SingleUp"`. Refer to `BgReading.slopeFromName()` for possible values.

Here is an example JSON payload with a single reading:
```java
final JSONArray sgv_array = new JSONArray();
final JSONObject sgv_object = new JSONObject();
sgv_object.put("type", "sgv");
sgv_object.put("date", 1526817691000.0);
sgv_object.put("sgv", 158);
sgv_object.put("direction", "SingleUp");
sgv_array.put(sgv_object);
intent.putExtra("data", sgv_array.toString());
```

A code example of this is in the unit test source tree in `NSEmulatorReceiverTest.bgReadingExampleBroadcast()`

### Sending the Intent
After constructing the intent, use `sendBroadcast()` to send it:
```java
context.sendBroadcast(intent);
```

## Testing the Integration
For testing purposes, make sure you set xDrip `Hardware Data Source` to `640G / Eversense` to enable the receiver.
7 changes: 3 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ dependencies {
exclude group: 'androidx.core', module: 'core' // fix INotificationSideChannel // android.support.v4.app
}

api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
implementation(name: 'ns-sdk-full-release', ext: 'aar')

//implementation 'androidx.core:core-ktx:1.9.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.7.21'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'

implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
Expand Down Expand Up @@ -302,7 +302,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
implementation 'com.getpebble:pebblekit:3.1.0'
implementation ("androidx.health.connect:connect-client:1.0.0-alpha07") {
implementation ("androidx.health.connect:connect-client:1.1.0-alpha06") {
exclude group: 'androidx.core', module: 'core'
}
implementation 'com.github.jamorham:amazfitcommunication:master-SNAPSHOT'
Expand Down Expand Up @@ -332,7 +332,6 @@ dependencies {
implementation(name: 'colorpicker', ext: 'aar')
implementation(name: 'hellocharts-library-release', ext: 'aar')
implementation(name: 'search-preference', ext: 'aar')
implementation(name: 'javakotlininterop', ext: 'aar')
implementation 'org.apache.commons:commons-text:1.3'
implementation 'com.google.dagger:dagger:2.25.4'
implementation "com.evernote:android-job:1.2.6"
Expand Down
Binary file removed app/libs/javakotlininterop.aar
Binary file not shown.
43 changes: 40 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

<uses-permission android:name="android.permission.MANAGE_HEALTH_DATA"/>
<uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS"/>

<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE"/>
<uses-permission android:name="android.permission.health.WRITE_BLOOD_GLUCOSE"/>
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_WEIGHT"/>
<uses-permission android:name="android.permission.health.READ_DISTANCE"/>
<uses-permission android:name="android.permission.health.READ_ELEVATION_GAINED"/>
<uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED"/>
<uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY"/>
<uses-permission android:name="android.permission.health.READ_HEIGHT"/>
<uses-permission android:name="android.permission.health.READ_HYDRATION"/>
<uses-permission android:name="android.permission.health.READ_NUTRITION"/>
<uses-permission android:name="android.permission.health.WRITE_NUTRITION"/>
<uses-permission android:name="android.permission.health.READ_POWER"/>
<uses-permission android:name="android.permission.health.READ_RESTING_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_SLEEP"/>
<uses-permission android:name="android.permission.health.READ_SPEED"/>
<uses-permission android:name="android.permission.health.READ_WHEELCHAIR_PUSHES"/>

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

<application
android:name=".xdrip"
android:allowBackup="false"
Expand All @@ -77,10 +103,21 @@
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
<meta-data
android:name="health_permissions"
android:resource="@array/health_permissions" />
</activity>
<activity-alias
android:name="AndroidURationaleActivity"
android:exported="true"
android:targetActivity=".HealthPrivacy">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
<!-- Permission handling for Android 14 -->
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
<category android:name="android.intent.category.HEALTH_PERMISSIONS"/>
</intent-filter>
</activity-alias>

<activity
android:name=".cloud.backup.BackupActivity"
android:exported="false"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/eveningoutpost/dexdrip/Home.java
Original file line number Diff line number Diff line change
Expand Up @@ -3560,7 +3560,7 @@ public void checkForUpdate(MenuItem myitem) {
if (JoH.ratelimit("manual-update-check", 5)) {
toast(getString(R.string.checking_for_update));
UpdateActivity.last_check_time = -1;
UpdateActivity.checkForAnUpdate(getApplicationContext());
UpdateActivity.checkForAnUpdate(getApplicationContext(), true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.os.PowerManager;

import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkAuthenticator;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.auth.CareLinkCredentialStore;
import com.eveningoutpost.dexdrip.models.JoH;
import com.eveningoutpost.dexdrip.models.UserError;
import com.eveningoutpost.dexdrip.utilitymodels.CollectionServiceStarter;
Expand Down Expand Up @@ -47,50 +49,71 @@ public String getStatus() {
this.carelinkCountry = carelinkCountry;
this.carelinkPatient = carelinkPatient;
loginDataLooksOkay = !emptyString(carelinkUsername) && !emptyString(carelinkPassword) && carelinkCountry != null && !emptyString(carelinkCountry);
loginDataLooksOkay = true;
}

public static void resetInstance() {
UserError.Log.d(TAG, "Instance reset");
CollectionServiceStarter.restartCollectionServiceBackground();
}

public boolean doEverything() {
msg("Start download");
public void doEverything(boolean refreshToken, boolean downloadData) {
if (refreshToken)
this.refreshToken();
if (downloadData)
this.downloadData();
}

if (D) UserError.Log.e(TAG, "doEverything called");
if (loginDataLooksOkay) {
private void downloadData() {
msg("Start download");
if (checkCredentials()) {
try {
if (getCareLinkClient() != null) {
extendWakeLock(30_000);
backgroundProcessConnectData();
} else {
UserError.Log.d(TAG, "Cannot get data as ConnectClient is null");
return false;
UserError.Log.d(TAG, "Cannot get data as CareLinkClient is null");
msg("Download data failed!");
}
return true;
} catch (Exception e) {
UserError.Log.e(TAG, "Got exception in getData() " + e);
releaseWakeLock();
return false;
msg("Download data failed!");
}
} else {
final String invalid = "Invalid CareLink login data!";
msg(invalid);
UserError.Log.e(TAG, invalid);
if (emptyString(carelinkUsername)) {
UserError.Log.e(TAG, "CareLink Username empty!");
}
if (emptyString(carelinkPassword)) {
UserError.Log.e(TAG, "CareLink Password empty!");
}
if (carelinkCountry == null) {
UserError.Log.e(TAG, "CareLink Country empty!");
} else if (!CountryUtils.isSupportedCountry(carelinkCountry)) {
UserError.Log.e(TAG, "CareLink Country not supported!");
}
}

private void refreshToken() {
msg("Start refreshing token");
if (checkCredentials()) {
try {
if (new CareLinkAuthenticator(CareLinkCredentialStore.getInstance().getCredential().country, CareLinkCredentialStore.getInstance()).refreshToken()) {
UserError.Log.d(TAG, "Login token renewed!");
msg(null);
} else {
UserError.Log.e(TAG, "Error renewing login token!");
msg("Login refresh failed! Will try again!");
}
} catch (Exception e) {
UserError.Log.e(TAG, "Error renewing login token: " + e.getMessage());
msg("Login refresh failed! Will try again!");
}
return false;
}
}

private boolean checkCredentials() {
// Not authenticated
if (CareLinkCredentialStore.getInstance().getAuthStatus() != CareLinkCredentialStore.AUTHENTICATED) {
msg("Not logged in! Please log in!");
return false;
// Token expired
} else if (CareLinkCredentialStore.getInstance().getExpiresIn() <= 0) {
msg("Login refresh expired! Please log in!");
return false;
// Credentials are all ok!
} else {
return true;
}
}

private void msg(final String msg) {
Expand Down Expand Up @@ -118,64 +141,44 @@ private void processConnectData() {
carelinkClient = getCareLinkClient();
//Get RecentData from CareLink client
if (carelinkClient != null) {
//Get data
try {
if (JoH.emptyString(this.carelinkPatient))
recentData = getCareLinkClient().getRecentData();
else
recentData = getCareLinkClient().getRecentData(this.carelinkPatient);
lastResponseCode = carelinkClient.getLastResponseCode();
} catch (Exception e) {
UserError.Log.e(TAG, "Exception in CareLink data download: " + e);
}

//Try twice in case of 401 error
for (int i = 0; i < 2; i++) {

//Get data
//Process data
if (recentData != null) {
UserError.Log.d(TAG, "Success get data!");
try {
if (JoH.emptyString(this.carelinkPatient))
recentData = getCareLinkClient().getRecentData();
else
recentData = getCareLinkClient().getRecentData(this.carelinkPatient);
lastResponseCode = carelinkClient.getLastResponseCode();
UserError.Log.d(TAG, "Start process data");
//Process CareLink data (conversion and update xDrip data)
CareLinkDataProcessor.processData(recentData, true);
UserError.Log.d(TAG, "ProcessData finished!");
//Update Service status
CareLinkFollowService.updateBgReceiveDelay();
msg(null);
} catch (Exception e) {
UserError.Log.e(TAG, "Exception in CareLink data download: " + e);
UserError.Log.e(TAG, "Exception in data processing: " + e);
msg("Data processing error!");
}

//Process data
if (recentData != null) {
UserError.Log.d(TAG, "Success get data!");
try {
UserError.Log.d(TAG, "Start process data");
//Process CareLink data (conversion and update xDrip data)
CareLinkDataProcessor.processData(recentData, true);
UserError.Log.d(TAG, "ProcessData finished!");
//Update Service status
CareLinkFollowService.updateBgReceiveDelay();
msg(null);
} catch (Exception e) {
UserError.Log.e(TAG, "Exception in data processing: " + e);
msg("Data processing error!");
}
//Data receive error
//Data receive error
} else {
if (carelinkClient.getLastResponseCode() == 401) {
UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode());
msg("Login error!");
//login error
} else {
//first 401 error => TRY AGAIN, only debug log
if (carelinkClient.getLastResponseCode() == 401 && i == 0) {
UserError.Log.d(TAG, "Try get data again due to 401 response code." + getCareLinkClient().getLastErrorMessage());
//second 401 error => unauthorized error
} else if (carelinkClient.getLastResponseCode() == 401) {
UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode());
msg("Login error!");
//login error
} else if (!getCareLinkClient().getLastLoginSuccess()) {
UserError.Log.e(TAG, "CareLink login error! Response code: " + carelinkClient.getLastResponseCode());
UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage());
msg("Login error!");
//other error in download
} else {
UserError.Log.e(TAG, "CareLink download error! Response code: " + carelinkClient.getLastResponseCode());
UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage());
msg("Data request error!");
}
UserError.Log.e(TAG, "CareLink download error! Response code: " + carelinkClient.getLastResponseCode());
UserError.Log.e(TAG, "Error message: " + getCareLinkClient().getLastErrorMessage());
msg("Download data failed!");
}

//Next try only for 401 error and first attempt
if (!(carelinkClient.getLastResponseCode() == 401 && i == 0))
break;

}

}

}
Expand All @@ -185,7 +188,8 @@ private CareLinkClient getCareLinkClient() {
if (careLinkClient == null) {
try {
UserError.Log.d(TAG, "Creating CareLinkClient");
careLinkClient = new CareLinkClient(carelinkUsername, carelinkPassword, carelinkCountry);
if (CareLinkCredentialStore.getInstance().getAuthStatus() == CareLinkCredentialStore.AUTHENTICATED)
careLinkClient = new CareLinkClient(CareLinkCredentialStore.getInstance());
} catch (Exception e) {
UserError.Log.e(TAG, "Error creating CareLinkClient", e);
}
Expand Down
Loading

0 comments on commit 39f41d5

Please sign in to comment.