Merge branch 'dev' into automation
This commit is contained in:
commit
09270f36bf
|
@ -14,7 +14,7 @@ android:
|
||||||
- extra-google-google_play_services
|
- extra-google-google_play_services
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- yes | sdkmanager "platforms;android-28"
|
#- yes | sdkmanager "platforms;android-28"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# Unit Test
|
# Unit Test
|
||||||
|
|
|
@ -285,9 +285,17 @@ dependencies {
|
||||||
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
|
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
|
||||||
testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}"
|
testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}"
|
||||||
testImplementation "joda-time:joda-time:2.9.9"
|
testImplementation "joda-time:joda-time:2.9.9"
|
||||||
testImplementation "com.google.truth:truth:0.39"
|
testImplementation("com.google.truth:truth:0.39") {
|
||||||
testImplementation 'org.robolectric:robolectric:4.2.1'
|
exclude group: "com.google.guava", module: "guava"
|
||||||
|
}
|
||||||
|
testImplementation("org.robolectric:robolectric:4.2.1") {
|
||||||
|
exclude group: "com.google.guava", module: "guava"
|
||||||
|
}
|
||||||
testImplementation "org.skyscreamer:jsonassert:1.5.0"
|
testImplementation "org.skyscreamer:jsonassert:1.5.0"
|
||||||
|
testImplementation "org.hamcrest:hamcrest-all:1.3"
|
||||||
|
testImplementation("uk.org.lidalia:slf4j-test:1.2.0") {
|
||||||
|
exclude group: "com.google.guava", module: "guava"
|
||||||
|
}
|
||||||
|
|
||||||
androidTestImplementation "org.mockito:mockito-core:2.8.47"
|
androidTestImplementation "org.mockito:mockito-core:2.8.47"
|
||||||
androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}"
|
androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}"
|
||||||
|
|
|
@ -288,6 +288,23 @@
|
||||||
android:theme="@style/AppTheme" />
|
android:theme="@style/AppTheme" />
|
||||||
<activity android:name=".activities.RequestDexcomPermissionActivity" />
|
<activity android:name=".activities.RequestDexcomPermissionActivity" />
|
||||||
|
|
||||||
|
<!-- Medtronic service and activities -->
|
||||||
|
<service
|
||||||
|
android:name=".plugins.pump.medtronic.service.RileyLinkMedtronicService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true" />
|
||||||
|
<activity android:name=".plugins.pump.common.dialog.RileyLinkBLEScanActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="info.nightscout.androidaps.plugins.PumpCommon.dialog.RileyLinkBLEScanActivity" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity"
|
||||||
|
android:label="@string/title_activity_rileylink_settings"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoTitle" />
|
||||||
|
<activity android:name=".plugins.pump.medtronic.dialog.MedtronicHistoryActivity" />
|
||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package info.nightscout.androidaps;
|
package info.nightscout.androidaps;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.PluralsRes;
|
import androidx.annotation.PluralsRes;
|
||||||
|
@ -64,12 +69,15 @@ import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin;
|
||||||
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
|
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
|
||||||
import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin;
|
import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin;
|
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
|
import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
|
import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin;
|
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
|
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
|
||||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
||||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
||||||
|
@ -87,6 +95,7 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||||
import info.nightscout.androidaps.receivers.DataReceiver;
|
import info.nightscout.androidaps.receivers.DataReceiver;
|
||||||
import info.nightscout.androidaps.receivers.KeepAliveReceiver;
|
import info.nightscout.androidaps.receivers.KeepAliveReceiver;
|
||||||
import info.nightscout.androidaps.receivers.NSAlarmReceiver;
|
import info.nightscout.androidaps.receivers.NSAlarmReceiver;
|
||||||
|
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver;
|
||||||
import info.nightscout.androidaps.services.Intents;
|
import info.nightscout.androidaps.services.Intents;
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||||
import io.fabric.sdk.android.Fabric;
|
import io.fabric.sdk.android.Fabric;
|
||||||
|
@ -114,6 +123,8 @@ public class MainApp extends Application {
|
||||||
private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver();
|
private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver();
|
||||||
private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver();
|
private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver();
|
||||||
private LocalBroadcastManager lbm;
|
private LocalBroadcastManager lbm;
|
||||||
|
BroadcastReceiver btReceiver;
|
||||||
|
TimeDateOrTZChangeReceiver timeDateOrTZChangeReceiver;
|
||||||
|
|
||||||
public static boolean devBranch;
|
public static boolean devBranch;
|
||||||
public static boolean engineeringMode;
|
public static boolean engineeringMode;
|
||||||
|
@ -155,6 +166,7 @@ public class MainApp extends Application {
|
||||||
|
|
||||||
//trigger here to see the new version on app start after an update
|
//trigger here to see the new version on app start after an update
|
||||||
triggerCheckVersion();
|
triggerCheckVersion();
|
||||||
|
//setBTReceiver();
|
||||||
|
|
||||||
if (pluginsList == null) {
|
if (pluginsList == null) {
|
||||||
pluginsList = new ArrayList<>();
|
pluginsList = new ArrayList<>();
|
||||||
|
@ -176,6 +188,7 @@ public class MainApp extends Application {
|
||||||
if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin());
|
if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin());
|
||||||
pluginsList.add(CareportalPlugin.getPlugin());
|
pluginsList.add(CareportalPlugin.getPlugin());
|
||||||
if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin());
|
if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin());
|
||||||
|
if (Config.PUMPDRIVERS && engineeringMode) pluginsList.add(MedtronicPumpPlugin.getPlugin());
|
||||||
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
|
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
|
||||||
pluginsList.add(VirtualPumpPlugin.getPlugin());
|
pluginsList.add(VirtualPumpPlugin.getPlugin());
|
||||||
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());
|
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());
|
||||||
|
@ -257,6 +270,10 @@ public class MainApp extends Application {
|
||||||
|
|
||||||
//register dbaccess
|
//register dbaccess
|
||||||
lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE));
|
lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE));
|
||||||
|
|
||||||
|
this.timeDateOrTZChangeReceiver = new TimeDateOrTZChangeReceiver();
|
||||||
|
this.timeDateOrTZChangeReceiver.registerBroadcasts(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startKeepAliveService() {
|
private void startKeepAliveService() {
|
||||||
|
@ -442,6 +459,15 @@ public class MainApp extends Application {
|
||||||
sDatabaseHelper.close();
|
sDatabaseHelper.close();
|
||||||
sDatabaseHelper = null;
|
sDatabaseHelper = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (btReceiver != null) {
|
||||||
|
unregisterReceiver(btReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeDateOrTZChangeReceiver!=null) {
|
||||||
|
unregisterReceiver(timeDateOrTZChangeReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int dpToPx(int dp) {
|
public static int dpToPx(int dp) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||||
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
|
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
|
||||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
|
||||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
||||||
|
@ -171,6 +172,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
|
||||||
addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP);
|
addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP);
|
||||||
addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP);
|
addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP);
|
||||||
addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP);
|
addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP);
|
||||||
|
addPreferencesFromResourceIfEnabled(MedtronicPumpPlugin.getPlugin(), PluginType.PUMP);
|
||||||
|
|
||||||
if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
||||||
|| DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
|| DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
||||||
|
|
|
@ -21,6 +21,8 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
@ -536,6 +538,24 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
return tddList;
|
return tddList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TDD> getTDDsForLastXDays(int days) {
|
||||||
|
List<TDD> tddList;
|
||||||
|
GregorianCalendar gc = new GregorianCalendar();
|
||||||
|
gc.add(Calendar.DAY_OF_YEAR, (-1) * days);
|
||||||
|
|
||||||
|
try {
|
||||||
|
QueryBuilder<TDD, String> queryBuilder = getDaoTDD().queryBuilder();
|
||||||
|
queryBuilder.orderBy("date", false);
|
||||||
|
Where<TDD, String> where = queryBuilder.where();
|
||||||
|
where.ge("date", gc.getTimeInMillis());
|
||||||
|
PreparedQuery<TDD> preparedQuery = queryBuilder.prepare();
|
||||||
|
tddList = getDaoTDD().query(preparedQuery);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Unhandled exception", e);
|
||||||
|
tddList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return tddList;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------- DbRequests handling -------------------
|
// ------------- DbRequests handling -------------------
|
||||||
|
|
||||||
|
@ -871,6 +891,31 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// search by date (in case its standard record that has become pump record)
|
||||||
|
QueryBuilder<TemporaryBasal, Long> queryBuilder2 = getDaoTemporaryBasal().queryBuilder();
|
||||||
|
Where where2 = queryBuilder2.where();
|
||||||
|
where2.eq("date", tempBasal.date);
|
||||||
|
PreparedQuery<TemporaryBasal> preparedQuery2 = queryBuilder2.prepare();
|
||||||
|
List<TemporaryBasal> trList2 = getDaoTemporaryBasal().query(preparedQuery2);
|
||||||
|
|
||||||
|
if (trList2.size() > 0) {
|
||||||
|
old = trList2.get(0);
|
||||||
|
|
||||||
|
old.copyFromPump(tempBasal);
|
||||||
|
old.source = Source.PUMP;
|
||||||
|
|
||||||
|
if (L.isEnabled(L.DATABASE))
|
||||||
|
log.debug("TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
||||||
|
|
||||||
|
getDaoTemporaryBasal().update(old);
|
||||||
|
|
||||||
|
updateEarliestDataChange(tempBasal.date);
|
||||||
|
scheduleTemporaryBasalChange();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
getDaoTemporaryBasal().create(tempBasal);
|
getDaoTemporaryBasal().create(tempBasal);
|
||||||
if (L.isEnabled(L.DATABASE))
|
if (L.isEnabled(L.DATABASE))
|
||||||
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
||||||
|
@ -1111,6 +1156,29 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TemporaryBasal findTempBasalByPumpId(Long pumpId) {
|
||||||
|
try {
|
||||||
|
QueryBuilder<TemporaryBasal, Long> queryBuilder = null;
|
||||||
|
queryBuilder = getDaoTemporaryBasal().queryBuilder();
|
||||||
|
queryBuilder.orderBy("date", false);
|
||||||
|
Where where = queryBuilder.where();
|
||||||
|
where.eq("pumpId", pumpId);
|
||||||
|
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
|
||||||
|
List<TemporaryBasal> list = getDaoTemporaryBasal().query(preparedQuery);
|
||||||
|
|
||||||
|
if (list.size() > 0)
|
||||||
|
return list.get(0);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Unhandled exception", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------ ExtendedBolus handling ---------------
|
// ------------ ExtendedBolus handling ---------------
|
||||||
|
|
||||||
public boolean createOrUpdate(ExtendedBolus extendedBolus) {
|
public boolean createOrUpdate(ExtendedBolus extendedBolus) {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package info.nightscout.androidaps.db;
|
||||||
|
|
||||||
|
public interface DbObjectBase {
|
||||||
|
|
||||||
|
long getDate();
|
||||||
|
|
||||||
|
long getPumpId();
|
||||||
|
|
||||||
|
}
|
|
@ -6,9 +6,8 @@ import com.j256.ormlite.table.DatabaseTable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.L;
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by mike on 20.09.2017.
|
* Created by mike on 20.09.2017.
|
||||||
|
@ -45,4 +44,16 @@ public class TDD {
|
||||||
this.basal = basal;
|
this.basal = basal;
|
||||||
this.total = total;
|
this.total = total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TDD [" +
|
||||||
|
"date=" + date +
|
||||||
|
"date(str)=" + DateTimeUtil.toStringFromTimeInMillis(date) +
|
||||||
|
", bolus=" + bolus +
|
||||||
|
", basal=" + basal +
|
||||||
|
", total=" + total +
|
||||||
|
']';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import info.nightscout.androidaps.utils.SP;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS)
|
@DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS)
|
||||||
public class TemporaryBasal implements Interval {
|
public class TemporaryBasal implements Interval, DbObjectBase {
|
||||||
private static Logger log = LoggerFactory.getLogger(L.DATABASE);
|
private static Logger log = LoggerFactory.getLogger(L.DATABASE);
|
||||||
|
|
||||||
@DatabaseField(id = true)
|
@DatabaseField(id = true)
|
||||||
|
@ -156,6 +156,14 @@ public class TemporaryBasal implements Interval {
|
||||||
netExtendedRate = t.netExtendedRate;
|
netExtendedRate = t.netExtendedRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void copyFromPump(TemporaryBasal t) {
|
||||||
|
durationInMinutes = t.durationInMinutes;
|
||||||
|
isAbsolute = t.isAbsolute;
|
||||||
|
percentRate = t.percentRate;
|
||||||
|
absoluteRate = t.absoluteRate;
|
||||||
|
pumpId = t.pumpId;
|
||||||
|
}
|
||||||
|
|
||||||
// -------- Interval interface ---------
|
// -------- Interval interface ---------
|
||||||
|
|
||||||
Long cuttedEnd = null;
|
Long cuttedEnd = null;
|
||||||
|
@ -416,4 +424,13 @@ public class TemporaryBasal implements Interval {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getPumpId() {
|
||||||
|
return this.pumpId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package info.nightscout.androidaps.events;
|
||||||
|
|
||||||
|
public class EventCustomActionsChanged extends Event {
|
||||||
|
}
|
|
@ -75,4 +75,10 @@ public interface PumpInterface {
|
||||||
|
|
||||||
void executeCustomAction(CustomActionType customActionType);
|
void executeCustomAction(CustomActionType customActionType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called when time or Timezone changes, and pump driver can then do a specific action (for
|
||||||
|
* example update clock on pump).
|
||||||
|
*/
|
||||||
|
void timeDateOrTimeZoneChanged();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ public interface TreatmentsInterface {
|
||||||
|
|
||||||
List<Treatment> getTreatmentsFromHistory();
|
List<Treatment> getTreatmentsFromHistory();
|
||||||
List<Treatment> getTreatments5MinBackFromHistory(long time);
|
List<Treatment> getTreatments5MinBackFromHistory(long time);
|
||||||
|
List<Treatment> getTreatmentsFromHistoryAfterTimestamp(long timestamp);
|
||||||
long getLastBolusTime();
|
long getLastBolusTime();
|
||||||
|
|
||||||
// real basals (not faked by extended bolus)
|
// real basals (not faked by extended bolus)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import info.nightscout.androidaps.activities.HistoryBrowseActivity;
|
||||||
import info.nightscout.androidaps.activities.TDDStatsActivity;
|
import info.nightscout.androidaps.activities.TDDStatsActivity;
|
||||||
import info.nightscout.androidaps.db.ExtendedBolus;
|
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||||
import info.nightscout.androidaps.db.TemporaryBasal;
|
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||||
|
import info.nightscout.androidaps.events.EventCustomActionsChanged;
|
||||||
import info.nightscout.androidaps.events.EventExtendedBolusChange;
|
import info.nightscout.androidaps.events.EventExtendedBolusChange;
|
||||||
import info.nightscout.androidaps.events.EventInitializationChanged;
|
import info.nightscout.androidaps.events.EventInitializationChanged;
|
||||||
import info.nightscout.androidaps.events.EventRefreshOverview;
|
import info.nightscout.androidaps.events.EventRefreshOverview;
|
||||||
|
@ -133,6 +134,11 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
||||||
updateGUI();
|
updateGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onStatusEvent(final EventCustomActionsChanged ev) {
|
||||||
|
updateGUI();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateGUI() {
|
protected void updateGUI() {
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
|
@ -247,6 +253,9 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
||||||
|
|
||||||
for (CustomAction customAction : customActions) {
|
for (CustomAction customAction : customActions) {
|
||||||
|
|
||||||
|
if (!customAction.isEnabled())
|
||||||
|
continue;
|
||||||
|
|
||||||
SingleClickButton btn = new SingleClickButton(getContext(), null, android.R.attr.buttonStyle);
|
SingleClickButton btn = new SingleClickButton(getContext(), null, android.R.attr.buttonStyle);
|
||||||
btn.setText(MainApp.gs(customAction.getName()));
|
btn.setText(MainApp.gs(customAction.getName()));
|
||||||
|
|
||||||
|
@ -264,10 +273,8 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
||||||
|
|
||||||
this.pumpCustomActions.put(MainApp.gs(customAction.getName()), customAction);
|
this.pumpCustomActions.put(MainApp.gs(customAction.getName()), customAction);
|
||||||
this.pumpCustomButtons.add(btn);
|
this.pumpCustomButtons.add(btn);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -283,7 +290,6 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
||||||
}
|
}
|
||||||
|
|
||||||
pumpCustomButtons.clear();
|
pumpCustomButtons.clear();
|
||||||
pumpCustomActions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,31 +12,36 @@ public class CustomAction {
|
||||||
private String iconName;
|
private String iconName;
|
||||||
private CustomActionType customActionType;
|
private CustomActionType customActionType;
|
||||||
private int iconResourceId;
|
private int iconResourceId;
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
|
||||||
public CustomAction(int nameResourceId, CustomActionType actionType) {
|
public CustomAction(int nameResourceId, CustomActionType actionType) {
|
||||||
this.name = nameResourceId;
|
this(nameResourceId, actionType, R.drawable.icon_actions_profileswitch, true);
|
||||||
this.customActionType = actionType;
|
|
||||||
this.iconResourceId = R.drawable.icon_actions_profileswitch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId) {
|
public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId) {
|
||||||
|
this(nameResourceId, actionType, iconResourceId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomAction(int nameResourceId, CustomActionType actionType, boolean enabled) {
|
||||||
|
this(nameResourceId, actionType, R.drawable.icon_actions_profileswitch, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId, boolean enabled) {
|
||||||
this.name = nameResourceId;
|
this.name = nameResourceId;
|
||||||
this.customActionType = actionType;
|
this.customActionType = actionType;
|
||||||
this.iconResourceId = iconResourceId;
|
this.iconResourceId = iconResourceId;
|
||||||
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getName() {
|
public int getName() {
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public CustomActionType getCustomActionType() {
|
public CustomActionType getCustomActionType() {
|
||||||
|
|
||||||
return customActionType;
|
return customActionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,4 +49,15 @@ public class CustomAction {
|
||||||
public int getIconResourceId() {
|
public int getIconResourceId() {
|
||||||
return iconResourceId;
|
return iconResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package info.nightscout.androidaps.plugins.general.overview.notifications;
|
package info.nightscout.androidaps.plugins.general.overview.notifications;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -76,6 +75,7 @@ public class Notification {
|
||||||
public static final int DISKFULL = 51;
|
public static final int DISKFULL = 51;
|
||||||
public static final int OLDVERSION = 52;
|
public static final int OLDVERSION = 52;
|
||||||
public static final int USERMESSAGE = 53;
|
public static final int USERMESSAGE = 53;
|
||||||
|
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54;
|
||||||
|
|
||||||
|
|
||||||
public int id;
|
public int id;
|
||||||
|
@ -86,6 +86,7 @@ public class Notification {
|
||||||
|
|
||||||
public NSAlarm nsAlarm = null;
|
public NSAlarm nsAlarm = null;
|
||||||
public Integer soundId = null;
|
public Integer soundId = null;
|
||||||
|
|
||||||
public Notification() {
|
public Notification() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,10 +200,10 @@ public class Notification {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isAlarmForStaleData(){
|
public static boolean isAlarmForStaleData() {
|
||||||
long snoozedTo = SP.getLong("snoozedTo", 0L);
|
long snoozedTo = SP.getLong("snoozedTo", 0L);
|
||||||
if(snoozedTo != 0L){
|
if (snoozedTo != 0L) {
|
||||||
if(System.currentTimeMillis() < SP.getLong("snoozedTo", 0L)) {
|
if (System.currentTimeMillis() < SP.getLong("snoozedTo", 0L)) {
|
||||||
//log.debug("Alarm is snoozed for next "+(SP.getLong("snoozedTo", 0L)-System.currentTimeMillis())/1000+" seconds");
|
//log.debug("Alarm is snoozed for next "+(SP.getLong("snoozedTo", 0L)-System.currentTimeMillis())/1000+" seconds");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -220,10 +221,10 @@ public class Notification {
|
||||||
Double threshold = NSSettingsStatus.getInstance().getThreshold("alarmTimeagoWarnMins");
|
Double threshold = NSSettingsStatus.getInstance().getThreshold("alarmTimeagoWarnMins");
|
||||||
//log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts);
|
//log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts);
|
||||||
// if no thresshold from Ns get it loccally
|
// if no thresshold from Ns get it loccally
|
||||||
if(threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue,15D);
|
if (threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue, 15D);
|
||||||
// No threshold of OpenAPS Alarm so using the one for BG
|
// No threshold of OpenAPS Alarm so using the one for BG
|
||||||
// Added OpenAPSEnabledAlerts to alarm check
|
// Added OpenAPSEnabledAlerts to alarm check
|
||||||
if((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false))||(bgReadingAgoMin > threshold && openAPSEnabledAlerts)){
|
if ((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false)) || (bgReadingAgoMin > threshold && openAPSEnabledAlerts)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//snoozing for threshold
|
//snoozing for threshold
|
||||||
|
|
|
@ -1408,4 +1408,10 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void timeDateOrTimeZoneChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,446 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.BuildConfig;
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.data.DetailedBolusInfo;
|
||||||
|
import info.nightscout.androidaps.data.Profile;
|
||||||
|
import info.nightscout.androidaps.data.PumpEnactResult;
|
||||||
|
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||||
|
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||||
|
import info.nightscout.androidaps.events.EventAppExit;
|
||||||
|
import info.nightscout.androidaps.interfaces.ConstraintsInterface;
|
||||||
|
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||||
|
import info.nightscout.androidaps.interfaces.PluginDescription;
|
||||||
|
import info.nightscout.androidaps.interfaces.PumpDescription;
|
||||||
|
import info.nightscout.androidaps.interfaces.PumpInterface;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
||||||
|
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||||
|
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||||
|
import info.nightscout.androidaps.utils.DateUtil;
|
||||||
|
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 23.04.18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// When using this class, make sure that your first step is to create mConnection (see MedtronicPumpPlugin)
|
||||||
|
|
||||||
|
public abstract class PumpPluginAbstract extends PluginBase implements PumpInterface, ConstraintsInterface {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||||
|
|
||||||
|
protected static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult().success(false)
|
||||||
|
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump_driver));
|
||||||
|
protected static final PumpEnactResult OPERATION_NOT_YET_SUPPORTED = new PumpEnactResult().success(false)
|
||||||
|
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump));
|
||||||
|
|
||||||
|
protected PumpDescription pumpDescription = new PumpDescription();
|
||||||
|
protected PumpStatus pumpStatus;
|
||||||
|
protected ServiceConnection serviceConnection = null;
|
||||||
|
protected boolean serviceRunning = false;
|
||||||
|
// protected boolean isInitialized = false;
|
||||||
|
protected PumpDriverState pumpState = PumpDriverState.NotInitialized;
|
||||||
|
protected boolean displayConnectionMessages = false;
|
||||||
|
|
||||||
|
|
||||||
|
protected PumpPluginAbstract(PluginDescription pluginDescription, PumpType pumpType) {
|
||||||
|
|
||||||
|
super(pluginDescription);
|
||||||
|
|
||||||
|
pumpDescription.setPumpDescription(pumpType);
|
||||||
|
|
||||||
|
initPumpStatusData();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void initPumpStatusData();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
Context context = MainApp.instance().getApplicationContext();
|
||||||
|
Intent intent = new Intent(context, getServiceClass());
|
||||||
|
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
|
serviceRunning = true;
|
||||||
|
|
||||||
|
MainApp.bus().register(this);
|
||||||
|
onStartCustomActions();
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
Context context = MainApp.instance().getApplicationContext();
|
||||||
|
context.unbindService(serviceConnection);
|
||||||
|
|
||||||
|
serviceRunning = false;
|
||||||
|
|
||||||
|
MainApp.bus().unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we need to run any custom actions in onStart (triggering events, etc)
|
||||||
|
*/
|
||||||
|
public abstract void onStartCustomActions();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class (same one you did serviceConnection for)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public abstract Class getServiceClass();
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedParameters")
|
||||||
|
@Subscribe
|
||||||
|
public void onStatusEvent(final EventAppExit e) {
|
||||||
|
MainApp.instance().getApplicationContext().unbindService(serviceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpStatus getPumpStatusData() {
|
||||||
|
return pumpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return PumpDriverState.isInitialized(pumpState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isSuspended() {
|
||||||
|
return pumpState == PumpDriverState.Suspended;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isBusy() {
|
||||||
|
return pumpState == PumpDriverState.Busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("isConnected [PumpPluginAbstract].");
|
||||||
|
return PumpDriverState.isConnected(pumpState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isConnecting() {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("isConnecting [PumpPluginAbstract].");
|
||||||
|
return pumpState == PumpDriverState.Connecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void connect(String reason) {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("connect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void disconnect(String reason) {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("disconnect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void stopConnecting() {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("stopConnecting [PumpPluginAbstract] - default (empty) implementation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHandshakeInProgress() {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("isHandshakeInProgress [PumpPluginAbstract] - default (empty) implementation.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finishHandshaking() {
|
||||||
|
if (displayConnectionMessages && isLoggingEnabled())
|
||||||
|
LOG.warn("finishHandshaking [PumpPluginAbstract] - default (empty) implementation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void getPumpStatus() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("getPumpStatus [PumpPluginAbstract] - Not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Upload to pump new basal profile
|
||||||
|
public PumpEnactResult setNewBasalProfile(Profile profile) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("setNewBasalProfile [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isThisProfileSet(Profile profile) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("isThisProfileSet [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public long lastDataTime() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("lastDataTime [PumpPluginAbstract].");
|
||||||
|
return pumpStatus.lastConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double getBaseBasalRate() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("getBaseBasalRate [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return 0.0d;
|
||||||
|
} // base basal rate, not temp basal
|
||||||
|
|
||||||
|
|
||||||
|
public void stopBolusDelivering() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("stopBolusDelivering [PumpPluginAbstract] - Not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile,
|
||||||
|
boolean enforceNew) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("setTempBasalAbsolute [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile,
|
||||||
|
boolean enforceNew) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("setTempBasalPercent [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("setExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// some pumps might set a very short temp close to 100% as cancelling a temp can be noisy
|
||||||
|
// when the cancel request is requested by the user (forced), the pump should always do a real cancel
|
||||||
|
|
||||||
|
public PumpEnactResult cancelTempBasal(boolean enforceNew) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("cancelTempBasal [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpEnactResult cancelExtendedBolus() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("cancelExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Status to be passed to NS
|
||||||
|
|
||||||
|
// public JSONObject getJSONStatus(Profile profile, String profileName) {
|
||||||
|
// return pumpDriver.getJSONStatus(profile, profileName);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public String deviceID() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("deviceID [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return "FakeDevice";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pump capabilities
|
||||||
|
|
||||||
|
public PumpDescription getPumpDescription() {
|
||||||
|
return pumpDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Short info for SMS, Wear etc
|
||||||
|
|
||||||
|
public boolean isFakingTempsByExtendedBoluses() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PumpEnactResult loadTDDs() {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("loadTDDs [PumpPluginAbstract] - Not implemented.");
|
||||||
|
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getJSONStatus(Profile profile, String profileName) {
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if ((pumpStatus.lastConnection + 5 * 60 * 1000L) < System.currentTimeMillis()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject pump = new JSONObject();
|
||||||
|
JSONObject battery = new JSONObject();
|
||||||
|
JSONObject status = new JSONObject();
|
||||||
|
JSONObject extended = new JSONObject();
|
||||||
|
try {
|
||||||
|
battery.put("percent", pumpStatus.batteryRemaining);
|
||||||
|
status.put("status", pumpStatus.pumpStatusType != null ? pumpStatus.pumpStatusType.getStatus() : "normal");
|
||||||
|
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
|
||||||
|
try {
|
||||||
|
extended.put("ActiveProfile", profileName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());
|
||||||
|
if (tb != null) {
|
||||||
|
extended.put("TempBasalAbsoluteRate",
|
||||||
|
tb.tempBasalConvertedToAbsolute(System.currentTimeMillis(), profile));
|
||||||
|
extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date));
|
||||||
|
extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis());
|
||||||
|
if (eb != null) {
|
||||||
|
extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate());
|
||||||
|
extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date));
|
||||||
|
extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes());
|
||||||
|
}
|
||||||
|
|
||||||
|
status.put("timestamp", DateUtil.toISOString(new Date()));
|
||||||
|
|
||||||
|
pump.put("battery", battery);
|
||||||
|
pump.put("status", status);
|
||||||
|
pump.put("extended", extended);
|
||||||
|
pump.put("reservoir", pumpStatus.reservoirRemainingUnits);
|
||||||
|
pump.put("clock", DateUtil.toISOString(new Date()));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LOG.error("Unhandled exception", e);
|
||||||
|
}
|
||||||
|
return pump;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME i18n, null checks: iob, TDD
|
||||||
|
@Override
|
||||||
|
public String shortStatus(boolean veryShort) {
|
||||||
|
String ret = "";
|
||||||
|
if (pumpStatus.lastConnection != 0) {
|
||||||
|
Long agoMsec = System.currentTimeMillis() - pumpStatus.lastConnection;
|
||||||
|
int agoMin = (int)(agoMsec / 60d / 1000d);
|
||||||
|
ret += "LastConn: " + agoMin + " min ago\n";
|
||||||
|
}
|
||||||
|
if (pumpStatus.lastBolusTime != null && pumpStatus.lastBolusTime.getTime() != 0) {
|
||||||
|
ret += "LastBolus: " + DecimalFormatter.to2Decimal(pumpStatus.lastBolusAmount) + "U @" + //
|
||||||
|
android.text.format.DateFormat.format("HH:mm", pumpStatus.lastBolusTime) + "\n";
|
||||||
|
}
|
||||||
|
TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin()
|
||||||
|
.getRealTempBasalFromHistory(System.currentTimeMillis());
|
||||||
|
if (activeTemp != null) {
|
||||||
|
ret += "Temp: " + activeTemp.toStringFull() + "\n";
|
||||||
|
}
|
||||||
|
ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(
|
||||||
|
System.currentTimeMillis());
|
||||||
|
if (activeExtendedBolus != null) {
|
||||||
|
ret += "Extended: " + activeExtendedBolus.toString() + "\n";
|
||||||
|
}
|
||||||
|
// if (!veryShort) {
|
||||||
|
// ret += "TDD: " + DecimalFormatter.to0Decimal(pumpStatus.dailyTotalUnits) + " / "
|
||||||
|
// + pumpStatus.maxDailyTotalUnits + " U\n";
|
||||||
|
// }
|
||||||
|
ret += "IOB: " + pumpStatus.iob + "U\n";
|
||||||
|
ret += "Reserv: " + DecimalFormatter.to0Decimal(pumpStatus.reservoirRemainingUnits) + "U\n";
|
||||||
|
ret += "Batt: " + pumpStatus.batteryRemaining + "\n";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
|
||||||
|
// neither carbs nor bolus requested
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.error("deliverTreatment: Invalid input");
|
||||||
|
return new PumpEnactResult().success(false).enacted(false).bolusDelivered(0d).carbsDelivered(0d)
|
||||||
|
.comment(MainApp.gs(R.string.danar_invalidinput));
|
||||||
|
} else if (detailedBolusInfo.insulin > 0) {
|
||||||
|
// bolus needed, ask pump to deliver it
|
||||||
|
return deliverBolus(detailedBolusInfo);
|
||||||
|
} else {
|
||||||
|
// no bolus required, carb only treatment
|
||||||
|
TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true);
|
||||||
|
|
||||||
|
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
|
||||||
|
bolusingEvent.t = new Treatment();
|
||||||
|
bolusingEvent.t.isSMB = detailedBolusInfo.isSMB;
|
||||||
|
bolusingEvent.percent = 100;
|
||||||
|
MainApp.bus().post(bolusingEvent);
|
||||||
|
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.debug("deliverTreatment: Carb only treatment.");
|
||||||
|
|
||||||
|
return new PumpEnactResult().success(true).enacted(true).bolusDelivered(0d)
|
||||||
|
.carbsDelivered(detailedBolusInfo.carbs).comment(MainApp.gs(R.string.virtualpump_resultok));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
triggerUIChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isLoggingEnabled() {
|
||||||
|
return L.isEnabled(L.PUMP);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo);
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void triggerUIChange();
|
||||||
|
|
||||||
|
|
||||||
|
public static PumpEnactResult getOperationNotSupportedWithCustomText(int resourceId) {
|
||||||
|
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(resourceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.data.ProfileStore;
|
||||||
|
import info.nightscout.androidaps.interfaces.PumpDescription;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 4/28/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class PumpStatus {
|
||||||
|
|
||||||
|
// connection
|
||||||
|
public LocalDateTime lastDataTime;
|
||||||
|
public long lastConnection = 0L;
|
||||||
|
public long previousConnection = 0L; // here should be stored last connection of previous session (so needs to be
|
||||||
|
// read before lastConnection is modified for first time).
|
||||||
|
|
||||||
|
// last bolus
|
||||||
|
public Date lastBolusTime;
|
||||||
|
public Double lastBolusAmount;
|
||||||
|
|
||||||
|
// other pump settings
|
||||||
|
public String activeProfileName = "0";
|
||||||
|
public double reservoirRemainingUnits = 0d;
|
||||||
|
public String reservoirFullUnits = "???";
|
||||||
|
public int batteryRemaining = 0; // percent, so 0-100
|
||||||
|
public Double batteryVoltage = null;
|
||||||
|
|
||||||
|
|
||||||
|
// iob
|
||||||
|
public String iob = null;
|
||||||
|
|
||||||
|
// TDD
|
||||||
|
public Double dailyTotalUnits;
|
||||||
|
public String maxDailyTotalUnits;
|
||||||
|
public boolean validBasalRateProfileSelectedOnPump = true;
|
||||||
|
public PumpType pumpType = PumpType.GenericAAPS;
|
||||||
|
public ProfileStore profileStore;
|
||||||
|
public String units; // Constants.MGDL or Constants.MMOL
|
||||||
|
public PumpStatusType pumpStatusType = PumpStatusType.Running;
|
||||||
|
public Double[] basalsByHour;
|
||||||
|
public double currentBasal = 0;
|
||||||
|
public int tempBasalInProgress = 0;
|
||||||
|
public int tempBasalRatio = 0;
|
||||||
|
public int tempBasalRemainMin = 0;
|
||||||
|
public Date tempBasalStart;
|
||||||
|
protected PumpDescription pumpDescription;
|
||||||
|
|
||||||
|
|
||||||
|
public PumpStatus(PumpDescription pumpDescription) {
|
||||||
|
this.pumpDescription = pumpDescription;
|
||||||
|
|
||||||
|
this.initSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void initSettings();
|
||||||
|
|
||||||
|
|
||||||
|
public void setLastCommunicationToNow() {
|
||||||
|
this.lastDataTime = LocalDateTime.now();
|
||||||
|
this.lastConnection = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract String getErrorInfo();
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void refreshConfiguration();
|
||||||
|
|
||||||
|
|
||||||
|
public PumpType getPumpType() {
|
||||||
|
return pumpType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setPumpType(PumpType pumpType) {
|
||||||
|
this.pumpType = pumpType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public Date last_bolus_time;
|
||||||
|
// public double last_bolus_amount = 0;
|
||||||
|
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ public enum PumpCapability {
|
||||||
DanaCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, TDD, ManualTDDLoad), //
|
DanaCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, TDD, ManualTDDLoad), //
|
||||||
DanaWithHistoryCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, StoreCarbInfo, TDD, ManualTDDLoad), //
|
DanaWithHistoryCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, StoreCarbInfo, TDD, ManualTDDLoad), //
|
||||||
InsightCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill,TDD,BasalRate30min), //
|
InsightCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill,TDD,BasalRate30min), //
|
||||||
|
MedtronicCapabilities(Bolus, TempBasal, BasalProfileSet, Refill, TDD), //
|
||||||
|
|
||||||
// BasalRates (separately grouped)
|
// BasalRates (separately grouped)
|
||||||
BasalRate_Duration15minAllowed, //
|
BasalRate_Duration15minAllowed, //
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 10/15/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum PumpDriverState {
|
||||||
|
|
||||||
|
NotInitialized, //
|
||||||
|
Connecting, //
|
||||||
|
Connected, //
|
||||||
|
Initialized, //
|
||||||
|
Ready,
|
||||||
|
Busy, //
|
||||||
|
Suspended, //
|
||||||
|
;
|
||||||
|
|
||||||
|
public static boolean isConnected(PumpDriverState pumpState) {
|
||||||
|
return pumpState == Connected || pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isInitialized(PumpDriverState pumpState) {
|
||||||
|
return pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/12/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum PumpStatusType {
|
||||||
|
Running("normal"), //
|
||||||
|
Suspended("suspended") //
|
||||||
|
;
|
||||||
|
|
||||||
|
private String statusString;
|
||||||
|
|
||||||
|
|
||||||
|
PumpStatusType(String statusString) {
|
||||||
|
this.statusString = statusString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return statusString;
|
||||||
|
}
|
||||||
|
}
|
|
@ -101,8 +101,8 @@ public enum PumpType {
|
||||||
Medtronic_512_712("Medtronic 512/712", ManufacturerType.Medtronic, "512/712", 0.05d, null, //
|
Medtronic_512_712("Medtronic 512/712", ManufacturerType.Medtronic, "512/712", 0.05d, null, //
|
||||||
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
||||||
PumpTempBasalType.Absolute, //
|
PumpTempBasalType.Absolute, //
|
||||||
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||||
0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), // TODO
|
0.05d, 0.05d, null, PumpCapability.MedtronicCapabilities), //
|
||||||
|
|
||||||
Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
|
Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
|
||||||
Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
|
Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
|
||||||
|
@ -110,8 +110,8 @@ public enum PumpType {
|
||||||
Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", ManufacturerType.Medtronic, "523/723 (Revel)", 0.05d, null, //
|
Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", ManufacturerType.Medtronic, "523/723 (Revel)", 0.05d, null, //
|
||||||
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
||||||
PumpTempBasalType.Absolute, //
|
PumpTempBasalType.Absolute, //
|
||||||
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||||
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), //
|
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.MedtronicCapabilities), //
|
||||||
|
|
||||||
Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO
|
Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.dialog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface RefreshableInterface {
|
||||||
|
|
||||||
|
void refreshData();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,423 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.dialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.le.BluetoothLeScanner;
|
||||||
|
import android.bluetooth.le.ScanCallback;
|
||||||
|
import android.bluetooth.le.ScanFilter;
|
||||||
|
import android.bluetooth.le.ScanResult;
|
||||||
|
import android.bluetooth.le.ScanSettings;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.LocationHelper;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example)
|
||||||
|
public class RileyLinkBLEScanActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RileyLinkBLEScanActivity.class);
|
||||||
|
|
||||||
|
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary.
|
||||||
|
private static final int REQUEST_ENABLE_BT = 30242; // arbitrary
|
||||||
|
|
||||||
|
private static String TAG = "RileyLinkBLEScanActivity";
|
||||||
|
|
||||||
|
// Stops scanning after 30 seconds.
|
||||||
|
private static final long SCAN_PERIOD = 30000;
|
||||||
|
public boolean mScanning;
|
||||||
|
public ScanSettings settings;
|
||||||
|
public List<ScanFilter> filters;
|
||||||
|
public ListView listBTScan;
|
||||||
|
public Toolbar toolbarBTScan;
|
||||||
|
public Context mContext = this;
|
||||||
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
|
private BluetoothLeScanner mLEScanner;
|
||||||
|
private LeDeviceListAdapter mLeDeviceListAdapter;
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
private String actionTitleStart, actionTitleStop;
|
||||||
|
private MenuItem menuItem;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.rileylink_scan_activity);
|
||||||
|
|
||||||
|
// Initializes Bluetooth adapter.
|
||||||
|
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
mHandler = new Handler();
|
||||||
|
|
||||||
|
mLeDeviceListAdapter = new LeDeviceListAdapter();
|
||||||
|
listBTScan = (ListView)findViewById(R.id.rileylink_listBTScan);
|
||||||
|
listBTScan.setAdapter(mLeDeviceListAdapter);
|
||||||
|
listBTScan.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
|
||||||
|
// stop scanning if still active
|
||||||
|
if (mScanning) {
|
||||||
|
mScanning = false;
|
||||||
|
mLEScanner.stopScan(mScanCallback2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView textview = (TextView)view.findViewById(R.id.rileylink_device_address);
|
||||||
|
String bleAddress = textview.getText().toString();
|
||||||
|
|
||||||
|
SP.putString(RileyLinkConst.Prefs.RileyLinkAddress, bleAddress);
|
||||||
|
|
||||||
|
RileyLinkUtil.getRileyLinkSelectPreference().setSummary(bleAddress);
|
||||||
|
|
||||||
|
MedtronicPumpStatus pumpStatus = MedtronicUtil.getPumpStatus();
|
||||||
|
pumpStatus.verifyConfiguration(); // force reloading of address
|
||||||
|
|
||||||
|
MainApp.bus().post(new EventMedtronicPumpConfigurationChanged());
|
||||||
|
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbarBTScan = (Toolbar)findViewById(R.id.rileylink_toolbarBTScan);
|
||||||
|
toolbarBTScan.setTitle(R.string.rileylink_scanner_title);
|
||||||
|
setSupportActionBar(toolbarBTScan);
|
||||||
|
|
||||||
|
prepareForScanning();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_rileylink_ble_scan, menu);
|
||||||
|
|
||||||
|
actionTitleStart = MainApp.gs(R.string.rileylink_scanner_scan_scan);
|
||||||
|
actionTitleStop = MainApp.gs(R.string.rileylink_scanner_scan_stop);
|
||||||
|
|
||||||
|
menuItem = menu.getItem(0);
|
||||||
|
|
||||||
|
menuItem.setTitle(actionTitleStart);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.rileylink_miScan: {
|
||||||
|
scanLeDevice(menuItem.getTitle().equals(actionTitleStart));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void prepareForScanning() {
|
||||||
|
// https://developer.android.com/training/permissions/requesting.html
|
||||||
|
// http://developer.radiusnetworks.com/2015/09/29/is-your-beacon-app-ready-for-android-6.html
|
||||||
|
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
|
||||||
|
Toast.makeText(this, R.string.rileylink_scanner_ble_not_supported, Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
// Use this check to determine whether BLE is supported on the device. Then
|
||||||
|
// you can selectively disable BLE-related features.
|
||||||
|
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// your code that requires permission
|
||||||
|
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION },
|
||||||
|
PERMISSION_REQUEST_COARSE_LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||||
|
// displays a dialog requesting user permission to enable Bluetooth.
|
||||||
|
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
|
||||||
|
Toast.makeText(this, R.string.rileylink_scanner_ble_not_enabled, Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
// Will request that GPS be enabled for devices running Marshmallow or newer.
|
||||||
|
if (!LocationHelper.isLocationEnabled(this)) {
|
||||||
|
LocationHelper.requestLocationForBluetooth(this);
|
||||||
|
}
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||||
|
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||||
|
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
|
||||||
|
filters = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
|
||||||
|
ParcelUuid.fromString(GattAttributes.SERVICE_RADIO)).build());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable currently selected RL, so that we can discover it
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == REQUEST_ENABLE_BT) {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
// User allowed Bluetooth to turn on
|
||||||
|
} else if (resultCode == RESULT_CANCELED) {
|
||||||
|
// Error, or user said "NO"
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScanCallback mScanCallback2 = new ScanCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanResult(int callbackType, final ScanResult scanRecord) {
|
||||||
|
|
||||||
|
Log.d(TAG, scanRecord.toString());
|
||||||
|
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (addDevice(scanRecord))
|
||||||
|
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBatchScanResults(final List<ScanResult> results) {
|
||||||
|
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
|
for (ScanResult result : results) {
|
||||||
|
if (addDevice(result))
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added)
|
||||||
|
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean addDevice(ScanResult result) {
|
||||||
|
|
||||||
|
BluetoothDevice device = result.getDevice();
|
||||||
|
|
||||||
|
List<ParcelUuid> serviceUuids = result.getScanRecord().getServiceUuids();
|
||||||
|
|
||||||
|
if (serviceUuids == null || serviceUuids.size() == 0) {
|
||||||
|
Log.v(TAG, "Device " + device.getAddress() + " has no serviceUuids (Not RileyLink).");
|
||||||
|
} else if (serviceUuids.size() > 1) {
|
||||||
|
Log.v(TAG, "Device " + device.getAddress() + " has too many serviceUuids (Not RileyLink).");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
String uuid = serviceUuids.get(0).getUuid().toString().toLowerCase();
|
||||||
|
|
||||||
|
if (uuid.equals(GattAttributes.SERVICE_RADIO)) {
|
||||||
|
Log.i(TAG, "Found RileyLink with address: " + device.getAddress());
|
||||||
|
mLeDeviceListAdapter.addDevice(result);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Device " + device.getAddress() + " has incorrect uuid (Not RileyLink).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getDeviceDebug(BluetoothDevice device) {
|
||||||
|
return "BluetoothDevice [name=" + device.getName() + ", address=" + device.getAddress() + //
|
||||||
|
", type=" + device.getType(); // + ", alias=" + device.getAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScanFailed(int errorCode) {
|
||||||
|
Log.e("Scan Failed", "Error Code: " + errorCode);
|
||||||
|
Toast.makeText(mContext, MainApp.gs(R.string.rileylink_scanner_scanning_error, errorCode),
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private void scanLeDevice(final boolean enable) {
|
||||||
|
|
||||||
|
if (mLEScanner == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
|
||||||
|
mLeDeviceListAdapter.clear();
|
||||||
|
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// Stops scanning after a pre-defined scan period.
|
||||||
|
mHandler.postDelayed(() -> {
|
||||||
|
|
||||||
|
if (mScanning) {
|
||||||
|
mScanning = false;
|
||||||
|
mLEScanner.stopScan(mScanCallback2);
|
||||||
|
LOG.debug("scanLeDevice: Scanning Stop");
|
||||||
|
Toast.makeText(mContext, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
|
||||||
|
menuItem.setTitle(actionTitleStart);
|
||||||
|
}
|
||||||
|
}, SCAN_PERIOD);
|
||||||
|
|
||||||
|
mScanning = true;
|
||||||
|
mLEScanner.startScan(filters, settings, mScanCallback2);
|
||||||
|
LOG.debug("scanLeDevice: Scanning Start");
|
||||||
|
Toast.makeText(this, R.string.rileylink_scanner_scanning, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
menuItem.setTitle(actionTitleStop);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (mScanning) {
|
||||||
|
mScanning = false;
|
||||||
|
mLEScanner.stopScan(mScanCallback2);
|
||||||
|
|
||||||
|
LOG.debug("scanLeDevice: Scanning Stop");
|
||||||
|
Toast.makeText(this, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
menuItem.setTitle(actionTitleStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LeDeviceListAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
private ArrayList<BluetoothDevice> mLeDevices;
|
||||||
|
private Map<BluetoothDevice, Integer> rileyLinkDevices;
|
||||||
|
private LayoutInflater mInflator;
|
||||||
|
String currentlySelectedAddress;
|
||||||
|
|
||||||
|
|
||||||
|
public LeDeviceListAdapter() {
|
||||||
|
super();
|
||||||
|
mLeDevices = new ArrayList<>();
|
||||||
|
rileyLinkDevices = new HashMap<>();
|
||||||
|
mInflator = RileyLinkBLEScanActivity.this.getLayoutInflater();
|
||||||
|
currentlySelectedAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addDevice(ScanResult result) {
|
||||||
|
|
||||||
|
if (!mLeDevices.contains(result.getDevice())) {
|
||||||
|
mLeDevices.add(result.getDevice());
|
||||||
|
}
|
||||||
|
rileyLinkDevices.put(result.getDevice(), result.getRssi());
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
mLeDevices.clear();
|
||||||
|
rileyLinkDevices.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mLeDevices.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int i) {
|
||||||
|
return mLeDevices.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int i) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||||
|
|
||||||
|
ViewHolder viewHolder;
|
||||||
|
// General ListView optimization code.
|
||||||
|
if (view == null) {
|
||||||
|
view = mInflator.inflate(R.layout.rileylink_scan_item, null);
|
||||||
|
viewHolder = new ViewHolder();
|
||||||
|
viewHolder.deviceAddress = (TextView)view.findViewById(R.id.rileylink_device_address);
|
||||||
|
viewHolder.deviceName = (TextView)view.findViewById(R.id.rileylink_device_name);
|
||||||
|
view.setTag(viewHolder);
|
||||||
|
} else {
|
||||||
|
viewHolder = (ViewHolder)view.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice device = mLeDevices.get(i);
|
||||||
|
String deviceName = device.getName();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(deviceName)) {
|
||||||
|
deviceName = "RileyLink";
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceName += " [" + rileyLinkDevices.get(device).intValue() + "]";
|
||||||
|
|
||||||
|
if (currentlySelectedAddress.equals(device.getAddress())) {
|
||||||
|
// viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light));
|
||||||
|
// viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light));
|
||||||
|
deviceName += " (" + getResources().getString(R.string.rileylink_scanner_selected_device) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
viewHolder.deviceName.setText(deviceName);
|
||||||
|
viewHolder.deviceAddress.setText(device.getAddress());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder {
|
||||||
|
|
||||||
|
TextView deviceName;
|
||||||
|
TextView deviceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,439 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyScanResults;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyTrial;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is abstract class for RileyLink Communication, this one needs to be extended by specific "Pump" class.
|
||||||
|
* <p>
|
||||||
|
* Created by andy on 5/10/18.
|
||||||
|
*/
|
||||||
|
public abstract class RileyLinkCommunicationManager {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||||
|
|
||||||
|
private static final int SCAN_TIMEOUT = 1500;
|
||||||
|
private static final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes
|
||||||
|
|
||||||
|
protected final RFSpy rfspy;
|
||||||
|
protected final Context context;
|
||||||
|
protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation
|
||||||
|
protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy))
|
||||||
|
protected long lastGoodReceiverCommunicationTime = 0;
|
||||||
|
protected PumpStatus pumpStatus;
|
||||||
|
protected RileyLinkServiceData rileyLinkServiceData;
|
||||||
|
private long nextWakeUpRequired = 0L;
|
||||||
|
|
||||||
|
// internal flag
|
||||||
|
private boolean showPumpMessages = true;
|
||||||
|
private int timeoutCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkCommunicationManager(Context context, RFSpy rfspy) {
|
||||||
|
this.context = context;
|
||||||
|
this.rfspy = rfspy;
|
||||||
|
this.rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||||
|
RileyLinkUtil.setRileyLinkCommunicationManager(this);
|
||||||
|
|
||||||
|
configurePumpSpecificSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void configurePumpSpecificSettings();
|
||||||
|
|
||||||
|
|
||||||
|
// All pump communications go through this function.
|
||||||
|
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Class<E> clazz)
|
||||||
|
throws RileyLinkCommunicationException {
|
||||||
|
|
||||||
|
if (showPumpMessages) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), timeout_ms);
|
||||||
|
|
||||||
|
RadioResponse radioResponse = rfSpyResponse.getRadioResponse();
|
||||||
|
|
||||||
|
E response = createResponseMessage(rfSpyResponse.getRadioResponse().getPayload(), clazz);
|
||||||
|
|
||||||
|
if (response.isValid()) {
|
||||||
|
// Mark this as the last time we heard from the pump.
|
||||||
|
rememberLastGoodDeviceCommunicationTime();
|
||||||
|
} else {
|
||||||
|
LOG.warn("isDeviceReachable. Response is invalid ! [interrupted={}, timeout={}, unknownCommand={}, invalidParam={}]", rfSpyResponse.wasInterrupted(),
|
||||||
|
rfSpyResponse.wasTimeout(), rfSpyResponse.isUnknownCommand(), rfSpyResponse.isInvalidParam());
|
||||||
|
|
||||||
|
if (rfSpyResponse.wasTimeout()) {
|
||||||
|
timeoutCount++;
|
||||||
|
|
||||||
|
long diff = System.currentTimeMillis() - pumpStatus.lastConnection;
|
||||||
|
|
||||||
|
if (diff > ALLOWED_PUMP_UNREACHABLE) {
|
||||||
|
LOG.warn("We reached max time that Pump can be unreachable. Starting Tuning.");
|
||||||
|
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
|
||||||
|
timeoutCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RileyLinkCommunicationException(RileyLinkBLEError.Timeout);
|
||||||
|
} else if (rfSpyResponse.wasInterrupted()) {
|
||||||
|
throw new RileyLinkCommunicationException(RileyLinkBLEError.Interrupted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPumpMessages) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Received:" + ByteUtil.shortHexString(rfSpyResponse.getRadioResponse().getPayload()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz);
|
||||||
|
|
||||||
|
|
||||||
|
public void wakeUp(boolean force) {
|
||||||
|
wakeUp(receiverDeviceAwakeForMinutes, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getNotConnectedCount() {
|
||||||
|
return rfspy != null ? rfspy.notConnectedCount : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME change wakeup
|
||||||
|
// TODO we might need to fix this. Maybe make pump awake for shorter time (battery factor for pump) - Andy
|
||||||
|
public void wakeUp(int duration_minutes, boolean force) {
|
||||||
|
// If it has been longer than n minutes, do wakeup. Otherwise assume pump is still awake.
|
||||||
|
// **** FIXME: this wakeup doesn't seem to work well... must revisit
|
||||||
|
// receiverDeviceAwakeForMinutes = duration_minutes;
|
||||||
|
|
||||||
|
MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp);
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
nextWakeUpRequired = 0L;
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > nextWakeUpRequired) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Waking pump...");
|
||||||
|
|
||||||
|
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple
|
||||||
|
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)200,
|
||||||
|
(byte)0, (byte)0, 25000, (byte)0);
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
|
||||||
|
// FIXME wakeUp successful !!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
nextWakeUpRequired = System.currentTimeMillis() + (receiverDeviceAwakeForMinutes * 60 * 1000);
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace("Last pump communication was recent, not waking pump.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// long lastGoodPlus = getLastGoodReceiverCommunicationTime() + (receiverDeviceAwakeForMinutes * 60 * 1000);
|
||||||
|
//
|
||||||
|
// if (System.currentTimeMillis() > lastGoodPlus || force) {
|
||||||
|
// LOG.info("Waking pump...");
|
||||||
|
//
|
||||||
|
// byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.PowerOn);
|
||||||
|
// RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, (byte)
|
||||||
|
// 0, (byte) 0, 15000, (byte) 0);
|
||||||
|
// LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
// } else {
|
||||||
|
// LOG.trace("Last pump communication was recent, not waking pump.");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setRadioFrequencyForPump(double freqMHz) {
|
||||||
|
rfspy.setBaseFrequency(freqMHz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double tuneForDevice() {
|
||||||
|
return scanForDevice(RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If user changes pump and one pump is running in US freq, and other in WW, then previously set frequency would be
|
||||||
|
* invalid,
|
||||||
|
* so we would need to retune. This checks that saved frequency is correct range.
|
||||||
|
*
|
||||||
|
* @param frequency
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValidFrequency(double frequency) {
|
||||||
|
|
||||||
|
double[] scanFrequencies = RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies();
|
||||||
|
|
||||||
|
if (scanFrequencies.length == 1) {
|
||||||
|
return RileyLinkUtil.isSame(scanFrequencies[0], frequency);
|
||||||
|
} else {
|
||||||
|
return (scanFrequencies[0] <= frequency && scanFrequencies[scanFrequencies.length - 1] >= frequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do device connection, with wakeup
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public abstract boolean tryToConnectToDevice();
|
||||||
|
|
||||||
|
|
||||||
|
public double scanForDevice(double[] frequencies) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Scanning for receiver ({})", receiverDeviceID);
|
||||||
|
wakeUp(receiverDeviceAwakeForMinutes, false);
|
||||||
|
FrequencyScanResults results = new FrequencyScanResults();
|
||||||
|
|
||||||
|
for (int i = 0; i < frequencies.length; i++) {
|
||||||
|
int tries = 3;
|
||||||
|
FrequencyTrial trial = new FrequencyTrial();
|
||||||
|
trial.frequencyMHz = frequencies[i];
|
||||||
|
rfspy.setBaseFrequency(frequencies[i]);
|
||||||
|
|
||||||
|
int sumRSSI = 0;
|
||||||
|
for (int j = 0; j < tries; j++) {
|
||||||
|
|
||||||
|
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
|
||||||
|
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)0,
|
||||||
|
(byte)0, (byte)0, 1250, (byte)0);
|
||||||
|
if (resp.wasTimeout()) {
|
||||||
|
LOG.error("scanForPump: Failed to find pump at frequency {}", frequencies[i]);
|
||||||
|
} else if (resp.looksLikeRadioPacket()) {
|
||||||
|
RadioResponse radioResponse = new RadioResponse();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
radioResponse.init(resp.getRaw());
|
||||||
|
|
||||||
|
if (radioResponse.isValid()) {
|
||||||
|
int rssi = calculateRssi(radioResponse.rssi);
|
||||||
|
sumRSSI += rssi;
|
||||||
|
trial.rssiList.add(rssi);
|
||||||
|
trial.successes++;
|
||||||
|
} else {
|
||||||
|
LOG.warn("Failed to parse radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
trial.rssiList.add(-99);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (RileyLinkCommunicationException rle) {
|
||||||
|
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
trial.rssiList.add(-99);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LOG.error("scanForPump: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
trial.rssiList.add(-99);
|
||||||
|
}
|
||||||
|
trial.tries++;
|
||||||
|
}
|
||||||
|
sumRSSI += -99.0 * (trial.tries - trial.successes);
|
||||||
|
trial.averageRSSI2 = (double)(sumRSSI) / (double)(trial.tries);
|
||||||
|
|
||||||
|
trial.calculateAverage();
|
||||||
|
|
||||||
|
results.trials.add(trial);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.dateTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder("Scan results:\n");
|
||||||
|
|
||||||
|
for (int k = 0; k < results.trials.size(); k++) {
|
||||||
|
FrequencyTrial one = results.trials.get(k);
|
||||||
|
|
||||||
|
stringBuilder.append(String.format("Scan Result[%s]: Freq=%s, avg RSSI = %s\n", "" + k, ""
|
||||||
|
+ one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info(stringBuilder.toString());
|
||||||
|
|
||||||
|
results.sort(); // sorts in ascending order
|
||||||
|
|
||||||
|
FrequencyTrial bestTrial = results.trials.get(results.trials.size() - 1);
|
||||||
|
results.bestFrequencyMHz = bestTrial.frequencyMHz;
|
||||||
|
if (bestTrial.successes > 0) {
|
||||||
|
rfspy.setBaseFrequency(results.bestFrequencyMHz);
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Best frequency found: " + results.bestFrequencyMHz);
|
||||||
|
return results.bestFrequencyMHz;
|
||||||
|
} else {
|
||||||
|
LOG.error("No pump response during scan.");
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int calculateRssi(int rssiIn) {
|
||||||
|
int rssiOffset = 73;
|
||||||
|
int outRssi = 0;
|
||||||
|
if (rssiIn >= 128) {
|
||||||
|
outRssi = ((rssiIn - 256) / 2) - rssiOffset;
|
||||||
|
} else {
|
||||||
|
outRssi = (rssiIn / 2) - rssiOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outRssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract byte[] createPumpMessageContent(RLMessageType type);
|
||||||
|
|
||||||
|
|
||||||
|
private int tune_tryFrequency(double freqMHz) {
|
||||||
|
rfspy.setBaseFrequency(freqMHz);
|
||||||
|
// RLMessage msg = makeRLMessage(RLMessageType.ReadSimpleData);
|
||||||
|
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
|
||||||
|
RadioPacket pkt = new RadioPacket(pumpMsgContent);
|
||||||
|
RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte)0, (byte)0, (byte)0, (byte)0, SCAN_TIMEOUT, (byte)0);
|
||||||
|
if (resp.wasTimeout()) {
|
||||||
|
LOG.warn("tune_tryFrequency: no pump response at frequency {}", freqMHz);
|
||||||
|
} else if (resp.looksLikeRadioPacket()) {
|
||||||
|
RadioResponse radioResponse = new RadioResponse();
|
||||||
|
try {
|
||||||
|
radioResponse.init(resp.getRaw());
|
||||||
|
|
||||||
|
if (radioResponse.isValid()) {
|
||||||
|
LOG.warn("tune_tryFrequency: saw response level {} at frequency {}", radioResponse.rssi, freqMHz);
|
||||||
|
return calculateRssi(radioResponse.rssi);
|
||||||
|
} else {
|
||||||
|
LOG.warn("tune_tryFrequency: invalid radio response:"
|
||||||
|
+ ByteUtil.shortHexString(radioResponse.getPayload()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (RileyLinkCommunicationException e) {
|
||||||
|
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double quickTuneForPump(double startFrequencyMHz) {
|
||||||
|
double betterFrequency = startFrequencyMHz;
|
||||||
|
double stepsize = 0.05;
|
||||||
|
for (int tries = 0; tries < 4; tries++) {
|
||||||
|
double evenBetterFrequency = quickTunePumpStep(betterFrequency, stepsize);
|
||||||
|
if (evenBetterFrequency == 0.0) {
|
||||||
|
// could not see the pump at all.
|
||||||
|
// Try again at larger step size
|
||||||
|
stepsize += 0.05;
|
||||||
|
} else {
|
||||||
|
if ((int)(evenBetterFrequency * 100) == (int)(betterFrequency * 100)) {
|
||||||
|
// value did not change, so we're done.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
betterFrequency = evenBetterFrequency; // and go again.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (betterFrequency == 0.0) {
|
||||||
|
// we've failed... caller should try a full scan for pump
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.error("quickTuneForPump: failed to find pump");
|
||||||
|
} else {
|
||||||
|
rfspy.setBaseFrequency(betterFrequency);
|
||||||
|
if (betterFrequency != startFrequencyMHz) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("quickTuneForPump: new frequency is {}MHz", betterFrequency);
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("quickTuneForPump: pump frequency is the same: {}MHz", startFrequencyMHz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return betterFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Doing quick radio tune for receiver ({})", receiverDeviceID);
|
||||||
|
wakeUp(false);
|
||||||
|
int startRssi = tune_tryFrequency(startFrequencyMHz);
|
||||||
|
double lowerFrequency = startFrequencyMHz - stepSizeMHz;
|
||||||
|
int lowerRssi = tune_tryFrequency(lowerFrequency);
|
||||||
|
double higherFrequency = startFrequencyMHz + stepSizeMHz;
|
||||||
|
int higherRssi = tune_tryFrequency(higherFrequency);
|
||||||
|
|
||||||
|
if ((higherRssi == 0.0) && (lowerRssi == 0.0) && (startRssi == 0.0)) {
|
||||||
|
// we can't see the pump at all...
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (higherRssi > startRssi) {
|
||||||
|
// need to move higher
|
||||||
|
return higherFrequency;
|
||||||
|
} else if (lowerRssi > startRssi) {
|
||||||
|
// need to move lower.
|
||||||
|
return lowerFrequency;
|
||||||
|
}
|
||||||
|
return startFrequencyMHz;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void rememberLastGoodDeviceCommunicationTime() {
|
||||||
|
lastGoodReceiverCommunicationTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime);
|
||||||
|
pumpStatus.setLastCommunicationToNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private long getLastGoodReceiverCommunicationTime() {
|
||||||
|
// If we have a value of zero, we need to load from prefs.
|
||||||
|
if (lastGoodReceiverCommunicationTime == 0L) {
|
||||||
|
lastGoodReceiverCommunicationTime = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L);
|
||||||
|
// Might still be zero, but that's fine.
|
||||||
|
}
|
||||||
|
double minutesAgo = (System.currentTimeMillis() - lastGoodReceiverCommunicationTime) / (1000.0 * 60.0);
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace("Last good pump communication was " + minutesAgo + " minutes ago.");
|
||||||
|
return lastGoodReceiverCommunicationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpStatus getPumpStatus() {
|
||||||
|
return pumpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clearNotConnectedCount() {
|
||||||
|
if (rfspy != null) {
|
||||||
|
rfspy.notConnectedCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 16/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkConst {
|
||||||
|
|
||||||
|
static final String Prefix = "AAPS.RileyLink.";
|
||||||
|
|
||||||
|
public class Intents {
|
||||||
|
|
||||||
|
public static final String RileyLinkReady = Prefix + "RileyLink_Ready";
|
||||||
|
public static final String RileyLinkGattFailed = Prefix + "RileyLink_Gatt_Failed";
|
||||||
|
|
||||||
|
public static final String BluetoothConnected = Prefix + "Bluetooth_Connected";
|
||||||
|
public static final String BluetoothReconnected = Prefix + "Bluetooth_Reconnected";
|
||||||
|
public static final String BluetoothDisconnected = Prefix + "Bluetooth_Disconnected";
|
||||||
|
public static final String RileyLinkDisconnected = Prefix + "RileyLink_Disconnected";
|
||||||
|
|
||||||
|
public static final String RileyLinkNewAddressSet = Prefix + "NewAddressSet";
|
||||||
|
|
||||||
|
public static final String INTENT_NEW_rileylinkAddressKey = Prefix + "INTENT_NEW_rileylinkAddressKey";
|
||||||
|
public static final String INTENT_NEW_pumpIDKey = Prefix + "INTENT_NEW_pumpIDKey";
|
||||||
|
public static final String RileyLinkDisconnect = Prefix + "RileyLink_Disconnect";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Prefs {
|
||||||
|
|
||||||
|
//public static final String PrefPrefix = "pref_rileylink_";
|
||||||
|
//public static final String RileyLinkAddress = PrefPrefix + "mac_address"; // pref_rileylink_mac_address
|
||||||
|
public static final int RileyLinkAddress = R.string.pref_key_rileylink_mac_address;
|
||||||
|
public static final String LastGoodDeviceCommunicationTime = Prefix + "lastGoodDeviceCommunicationTime";
|
||||||
|
public static final String LastGoodDeviceFrequency = Prefix + "LastGoodDeviceFrequency";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IPC {
|
||||||
|
|
||||||
|
// needs to br renamed (and maybe removed)
|
||||||
|
public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune";
|
||||||
|
public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump";
|
||||||
|
public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory";
|
||||||
|
public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory";
|
||||||
|
|
||||||
|
public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6b;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6bGeoff;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.BleAdvertisedData;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceNotification;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.ui.RileyLinkSelectPreference;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 17/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkUtil {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||||
|
protected static List<RLHistoryItem> historyRileyLink = new ArrayList<>();
|
||||||
|
protected static RileyLinkCommunicationManager rileyLinkCommunicationManager;
|
||||||
|
static ServiceTask currentTask;
|
||||||
|
private static Context context;
|
||||||
|
private static RileyLinkBLE rileyLinkBLE;
|
||||||
|
private static RileyLinkServiceData rileyLinkServiceData;
|
||||||
|
private static RileyLinkService rileyLinkService;
|
||||||
|
private static RileyLinkTargetFrequency rileyLinkTargetFrequency;
|
||||||
|
|
||||||
|
private static RileyLinkTargetDevice targetDevice;
|
||||||
|
private static RileyLinkEncodingType encoding;
|
||||||
|
private static RileyLinkSelectPreference rileyLinkSelectPreference;
|
||||||
|
private static Encoding4b6b encoding4b6b;
|
||||||
|
private static RileyLinkFirmwareVersion firmwareVersion;
|
||||||
|
|
||||||
|
|
||||||
|
public static void setContext(Context contextIn) {
|
||||||
|
RileyLinkUtil.context = contextIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkEncodingType getEncoding() {
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setEncoding(RileyLinkEncodingType encoding) {
|
||||||
|
RileyLinkUtil.encoding = encoding;
|
||||||
|
|
||||||
|
if (encoding == RileyLinkEncodingType.FourByteSixByteLocal) {
|
||||||
|
RileyLinkUtil.encoding4b6b = new Encoding4b6bGeoff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void sendBroadcastMessage(String message) {
|
||||||
|
if (context != null) {
|
||||||
|
Intent intent = new Intent(message);
|
||||||
|
LocalBroadcastManager.getInstance(RileyLinkUtil.context).sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setServiceState(RileyLinkServiceState newState) {
|
||||||
|
setServiceState(newState, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkError getError() {
|
||||||
|
if (RileyLinkUtil.rileyLinkServiceData != null)
|
||||||
|
return RileyLinkUtil.rileyLinkServiceData.errorCode;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkServiceState getServiceState() {
|
||||||
|
return workWithServiceState(null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setServiceState(RileyLinkServiceState newState, RileyLinkError errorCode) {
|
||||||
|
workWithServiceState(newState, errorCode, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static synchronized RileyLinkServiceState workWithServiceState(RileyLinkServiceState newState,
|
||||||
|
RileyLinkError errorCode, boolean set) {
|
||||||
|
|
||||||
|
if (set) {
|
||||||
|
|
||||||
|
RileyLinkUtil.rileyLinkServiceData.serviceState = newState;
|
||||||
|
RileyLinkUtil.rileyLinkServiceData.errorCode = errorCode;
|
||||||
|
|
||||||
|
if (L.isEnabled(L.PUMP))
|
||||||
|
LOG.info("RileyLink State Changed: {} {}", newState, errorCode == null ? "" : " - Error State: "
|
||||||
|
+ errorCode.name());
|
||||||
|
|
||||||
|
RileyLinkUtil.historyRileyLink.add(new RLHistoryItem(RileyLinkUtil.rileyLinkServiceData.serviceState,
|
||||||
|
RileyLinkUtil.rileyLinkServiceData.errorCode, targetDevice));
|
||||||
|
MainApp.bus().post(new EventMedtronicDeviceStatusChange(newState, errorCode));
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return (RileyLinkUtil.rileyLinkServiceData == null || RileyLinkUtil.rileyLinkServiceData.serviceState == null) ? //
|
||||||
|
RileyLinkServiceState.NotStarted
|
||||||
|
: RileyLinkUtil.rileyLinkServiceData.serviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkBLE getRileyLinkBLE() {
|
||||||
|
return RileyLinkUtil.rileyLinkBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkBLE(RileyLinkBLE rileyLinkBLEIn) {
|
||||||
|
RileyLinkUtil.rileyLinkBLE = rileyLinkBLEIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkServiceData getRileyLinkServiceData() {
|
||||||
|
return RileyLinkUtil.rileyLinkServiceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkServiceData(RileyLinkServiceData rileyLinkServiceData) {
|
||||||
|
RileyLinkUtil.rileyLinkServiceData = rileyLinkServiceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean hasPumpBeenTunned() {
|
||||||
|
return RileyLinkUtil.rileyLinkServiceData.tuneUpDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkService getRileyLinkService() {
|
||||||
|
return RileyLinkUtil.rileyLinkService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkService(RileyLinkService rileyLinkService) {
|
||||||
|
RileyLinkUtil.rileyLinkService = rileyLinkService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkCommunicationManager getRileyLinkCommunicationManager() {
|
||||||
|
return RileyLinkUtil.rileyLinkCommunicationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkCommunicationManager(RileyLinkCommunicationManager rileyLinkCommunicationManager) {
|
||||||
|
RileyLinkUtil.rileyLinkCommunicationManager = rileyLinkCommunicationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean sendNotification(ServiceNotification notification, Integer clientHashcode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME remove ?
|
||||||
|
public static void setCurrentTask(ServiceTask task) {
|
||||||
|
if (currentTask == null) {
|
||||||
|
currentTask = task;
|
||||||
|
} else {
|
||||||
|
//LOG.error("setCurrentTask: Cannot replace current task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void finishCurrentTask(ServiceTask task) {
|
||||||
|
if (task != currentTask) {
|
||||||
|
//LOG.error("finishCurrentTask: task does not match");
|
||||||
|
}
|
||||||
|
// hack to force deep copy of transport contents
|
||||||
|
ServiceTransport transport = task.getServiceTransport().clone();
|
||||||
|
|
||||||
|
if (transport.hasServiceResult()) {
|
||||||
|
sendServiceTransportResponse(transport, transport.getServiceResult());
|
||||||
|
}
|
||||||
|
currentTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) {
|
||||||
|
// get the key (hashcode) of the client who requested this
|
||||||
|
Integer clientHashcode = transport.getSenderHashcode();
|
||||||
|
// make a new bundle to send as the message data
|
||||||
|
transport.setServiceResult(serviceResult);
|
||||||
|
// FIXME
|
||||||
|
// transport.setTransportType(RT2Const.IPC.MSG_ServiceResult);
|
||||||
|
// rileyLinkIPCConnection.sendTransport(transport, clientHashcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkTargetFrequency getRileyLinkTargetFrequency() {
|
||||||
|
return RileyLinkUtil.rileyLinkTargetFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkTargetFrequency(RileyLinkTargetFrequency rileyLinkTargetFrequency) {
|
||||||
|
RileyLinkUtil.rileyLinkTargetFrequency = rileyLinkTargetFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSame(Double d1, Double d2) {
|
||||||
|
double diff = d1 - d2;
|
||||||
|
|
||||||
|
return (Math.abs(diff) <= 0.000001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) {
|
||||||
|
List<UUID> uuids = new ArrayList<UUID>();
|
||||||
|
String name = null;
|
||||||
|
if (advertisedData == null) {
|
||||||
|
return new BleAdvertisedData(uuids, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
while (buffer.remaining() > 2) {
|
||||||
|
byte length = buffer.get();
|
||||||
|
if (length == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
byte type = buffer.get();
|
||||||
|
switch (type) {
|
||||||
|
case 0x02: // Partial list of 16-bit UUIDs
|
||||||
|
case 0x03: // Complete list of 16-bit UUIDs
|
||||||
|
while (length >= 2) {
|
||||||
|
uuids
|
||||||
|
.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
|
||||||
|
length -= 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x06: // Partial list of 128-bit UUIDs
|
||||||
|
case 0x07: // Complete list of 128-bit UUIDs
|
||||||
|
while (length >= 16) {
|
||||||
|
long lsb = buffer.getLong();
|
||||||
|
long msb = buffer.getLong();
|
||||||
|
uuids.add(new UUID(msb, lsb));
|
||||||
|
length -= 16;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x09:
|
||||||
|
byte[] nameBytes = new byte[length - 1];
|
||||||
|
buffer.get(nameBytes);
|
||||||
|
try {
|
||||||
|
name = new String(nameBytes, "utf-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
buffer.position(buffer.position() + length - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new BleAdvertisedData(uuids, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<RLHistoryItem> getRileyLinkHistory() {
|
||||||
|
return historyRileyLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkTargetDevice getTargetDevice() {
|
||||||
|
return targetDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setTargetDevice(RileyLinkTargetDevice targetDevice) {
|
||||||
|
RileyLinkUtil.targetDevice = targetDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setRileyLinkSelectPreference(RileyLinkSelectPreference rileyLinkSelectPreference) {
|
||||||
|
|
||||||
|
RileyLinkUtil.rileyLinkSelectPreference = rileyLinkSelectPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkSelectPreference getRileyLinkSelectPreference() {
|
||||||
|
|
||||||
|
return rileyLinkSelectPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Encoding4b6b getEncoding4b6b() {
|
||||||
|
return RileyLinkUtil.encoding4b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setFirmwareVersion(RileyLinkFirmwareVersion firmwareVersion) {
|
||||||
|
RileyLinkUtil.firmwareVersion = firmwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkFirmwareVersion getFirmwareVersion() {
|
||||||
|
return firmwareVersion;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,438 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.Reset;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SendAndListen;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetHardwareEncoding;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetPreamble;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.UpdateRegister;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RXFilterMode;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class RFSpy {
|
||||||
|
|
||||||
|
public static final long RILEYLINK_FREQ_XTAL = 24000000;
|
||||||
|
public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 7500; // 1500
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
public int notConnectedCount = 0;
|
||||||
|
private RileyLinkBLE rileyLinkBle;
|
||||||
|
private RFSpyReader reader;
|
||||||
|
private RileyLinkTargetFrequency selectedTargetFrequency;
|
||||||
|
private UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
|
||||||
|
private UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
|
||||||
|
private UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION);
|
||||||
|
private UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT);
|
||||||
|
private RileyLinkFirmwareVersion firmwareVersion;
|
||||||
|
private String bleVersion; // We don't use it so no need of sofisticated logic
|
||||||
|
Double currentFrequencyMHz;
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpy(RileyLinkBLE rileyLinkBle) {
|
||||||
|
this.rileyLinkBle = rileyLinkBle;
|
||||||
|
reader = new RFSpyReader(rileyLinkBle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkFirmwareVersion getRLVersionCached() {
|
||||||
|
return firmwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getBLEVersionCached() {
|
||||||
|
return bleVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Call this after the RL services are discovered.
|
||||||
|
// Starts an async task to read when data is available
|
||||||
|
public void startReader() {
|
||||||
|
rileyLinkBle.registerRadioResponseCountNotification(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
newDataIsAvailable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reader.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Here should go generic RL initialisation + protocol adjustments depending on
|
||||||
|
// firmware version
|
||||||
|
public void initializeRileyLink() {
|
||||||
|
bleVersion = getVersion();
|
||||||
|
firmwareVersion = getFirmwareVersion();
|
||||||
|
RileyLinkUtil.setFirmwareVersion(firmwareVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Call this from the "response count" notification handler.
|
||||||
|
public void newDataIsAvailable() {
|
||||||
|
// pass the message to the reader (which should be internal to RFSpy)
|
||||||
|
reader.newDataIsAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This gets the version from the BLE113, not from the CC1110.
|
||||||
|
// I.e., this gets the version from the BLE interface, not from the radio.
|
||||||
|
public String getVersion() {
|
||||||
|
BLECommOperationResult result = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID, radioVersionUUID);
|
||||||
|
if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
|
||||||
|
String version = StringUtil.fromBytes(result.value);
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("BLE Version: " + version);
|
||||||
|
return version;
|
||||||
|
} else {
|
||||||
|
LOG.error("getVersion failed with code: " + result.resultCode);
|
||||||
|
return "(null)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkFirmwareVersion getFirmwareVersion() {
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Firmware Version. Get Version - Start");
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
// We have to call raw version of communication to get firmware version
|
||||||
|
// So that we can adjust other commands accordingly afterwords
|
||||||
|
|
||||||
|
byte[] getVersionRaw = getByteArray(RileyLinkCommandType.GetVersion.code);
|
||||||
|
byte[] response = writeToDataRaw(getVersionRaw, 5000);
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Firmware Version. GetVersion [response={}]", ByteUtil.shortHexString(response));
|
||||||
|
|
||||||
|
if (response != null) { // && response[0] == (byte) 0xDD) {
|
||||||
|
|
||||||
|
String versionString = StringUtil.fromBytes(response);
|
||||||
|
|
||||||
|
RileyLinkFirmwareVersion version = RileyLinkFirmwareVersion.getByVersionString(StringUtil
|
||||||
|
.fromBytes(response));
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace("Firmware Version string: {}, resolved to {}.", versionString, version);
|
||||||
|
|
||||||
|
if (version != RileyLinkFirmwareVersion.UnknownVersion)
|
||||||
|
return version;
|
||||||
|
|
||||||
|
SystemClock.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.error("Firmware Version can't be determined. Checking with BLE Version [{}].", bleVersion);
|
||||||
|
|
||||||
|
if (bleVersion.contains(" 2.")) {
|
||||||
|
return RileyLinkFirmwareVersion.Version_2_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RileyLinkFirmwareVersion.UnknownVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] writeToDataRaw(byte[] bytes, int responseTimeout_ms) {
|
||||||
|
SystemClock.sleep(100);
|
||||||
|
// FIXME drain read queue?
|
||||||
|
byte[] junkInBuffer = reader.poll(0);
|
||||||
|
|
||||||
|
while (junkInBuffer != null) {
|
||||||
|
LOG.warn(ThreadUtil.sig() + "writeToData: draining read queue, found this: "
|
||||||
|
+ ByteUtil.shortHexString(junkInBuffer));
|
||||||
|
junkInBuffer = reader.poll(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend length, and send it.
|
||||||
|
byte[] prepended = ByteUtil.concat(new byte[]{(byte) (bytes.length)}, bytes);
|
||||||
|
|
||||||
|
LOG.debug("writeToData (raw={})", ByteUtil.shortHexString(prepended));
|
||||||
|
|
||||||
|
BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID, radioDataUUID,
|
||||||
|
prepended);
|
||||||
|
if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
|
||||||
|
LOG.error("BLE Write operation failed, code=" + writeCheck.resultCode);
|
||||||
|
return null; // will be a null (invalid) response
|
||||||
|
}
|
||||||
|
SystemClock.sleep(100);
|
||||||
|
// Log.i(TAG,ThreadUtil.sig()+String.format(" writeToData:(timeout %d) %s",(responseTimeout_ms),ByteUtil.shortHexString(prepended)));
|
||||||
|
byte[] rawResponse = reader.poll(responseTimeout_ms);
|
||||||
|
return rawResponse;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The caller has to know how long the RFSpy will be busy with what was sent to it.
|
||||||
|
private RFSpyResponse writeToData(RileyLinkCommand command, int responseTimeout_ms) {
|
||||||
|
|
||||||
|
byte[] bytes = command.getRaw();
|
||||||
|
byte[] rawResponse = writeToDataRaw(bytes, responseTimeout_ms);
|
||||||
|
|
||||||
|
RFSpyResponse resp = new RFSpyResponse(command, rawResponse);
|
||||||
|
if (rawResponse == null) {
|
||||||
|
LOG.error("writeToData: No response from RileyLink");
|
||||||
|
notConnectedCount++;
|
||||||
|
} else {
|
||||||
|
if (resp.wasInterrupted()) {
|
||||||
|
LOG.error("writeToData: RileyLink was interrupted");
|
||||||
|
} else if (resp.wasTimeout()) {
|
||||||
|
LOG.error("writeToData: RileyLink reports timeout");
|
||||||
|
notConnectedCount++;
|
||||||
|
} else if (resp.isOK()) {
|
||||||
|
LOG.warn("writeToData: RileyLink reports OK");
|
||||||
|
resetNotConnectedCount();
|
||||||
|
} else {
|
||||||
|
if (resp.looksLikeRadioPacket()) {
|
||||||
|
// RadioResponse radioResp = resp.getRadioResponse();
|
||||||
|
// byte[] responsePayload = radioResp.getPayload();
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace("writeToData: received radio response. Will decode at upper level");
|
||||||
|
resetNotConnectedCount();
|
||||||
|
}
|
||||||
|
// Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void resetNotConnectedCount() {
|
||||||
|
this.notConnectedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] getByteArray(byte... input) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] getCommandArray(RileyLinkCommandType command, byte[] body) {
|
||||||
|
int bodyLength = body == null ? 0 : body.length;
|
||||||
|
|
||||||
|
byte[] output = new byte[bodyLength + 1];
|
||||||
|
|
||||||
|
output[0] = command.code;
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
for (int i = 0; i < body.length; i++) {
|
||||||
|
output[i + 1] = body[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
|
||||||
|
byte listenChannel, int timeout_ms, byte retryCount) {
|
||||||
|
return transmitThenReceive(pkt, sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms, retryCount, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse transmitThenReceive(RadioPacket pkt, int timeout_ms) {
|
||||||
|
return transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, timeout_ms, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
|
||||||
|
byte listenChannel, int timeout_ms, byte retryCount, Integer extendPreamble_ms) {
|
||||||
|
|
||||||
|
int sendDelay = repeatCount * delay_ms;
|
||||||
|
int receiveDelay = timeout_ms * (retryCount + 1);
|
||||||
|
|
||||||
|
SendAndListen command = new SendAndListen(sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms,
|
||||||
|
retryCount, extendPreamble_ms, pkt);
|
||||||
|
|
||||||
|
return writeToData(command, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse updateRegister(CC111XRegister reg, int val) {
|
||||||
|
RFSpyResponse resp = writeToData(new UpdateRegister(reg, (byte) val), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setBaseFrequency(double freqMHz) {
|
||||||
|
int value = (int) (freqMHz * 1000000 / ((double) (RILEYLINK_FREQ_XTAL) / Math.pow(2.0, 16.0)));
|
||||||
|
updateRegister(CC111XRegister.freq0, (byte) (value & 0xff));
|
||||||
|
updateRegister(CC111XRegister.freq1, (byte) ((value >> 8) & 0xff));
|
||||||
|
updateRegister(CC111XRegister.freq2, (byte) ((value >> 16) & 0xff));
|
||||||
|
LOG.info("Set frequency to {} MHz", freqMHz);
|
||||||
|
|
||||||
|
this.currentFrequencyMHz = freqMHz;
|
||||||
|
|
||||||
|
configureRadioForRegion(RileyLinkUtil.getRileyLinkTargetFrequency());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void configureRadioForRegion(RileyLinkTargetFrequency frequency) {
|
||||||
|
|
||||||
|
// we update registers only on first run, or if region changed
|
||||||
|
|
||||||
|
switch (frequency) {
|
||||||
|
case Medtronic_WorldWide: {
|
||||||
|
// updateRegister(CC111X_MDMCFG4, (byte) 0x59);
|
||||||
|
setRXFilterMode(RXFilterMode.Wide);
|
||||||
|
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
|
||||||
|
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
|
||||||
|
updateRegister(CC111XRegister.mdmcfg1, 0x62);
|
||||||
|
updateRegister(CC111XRegister.mdmcfg0, 0x1A);
|
||||||
|
updateRegister(CC111XRegister.deviatn, 0x13);
|
||||||
|
setMedtronicEncoding();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Medtronic_US: {
|
||||||
|
// updateRegister(CC111X_MDMCFG4, (byte) 0x99);
|
||||||
|
setRXFilterMode(RXFilterMode.Narrow);
|
||||||
|
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
|
||||||
|
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
|
||||||
|
updateRegister(CC111XRegister.mdmcfg1, 0x61);
|
||||||
|
updateRegister(CC111XRegister.mdmcfg0, 0x7E);
|
||||||
|
updateRegister(CC111XRegister.deviatn, 0x15);
|
||||||
|
setMedtronicEncoding();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Omnipod: {
|
||||||
|
RFSpyResponse r = null;
|
||||||
|
// RL initialization for Omnipod is a copy/paste from OmniKit implementation.
|
||||||
|
// Last commit from original repository: 5c3beb4144
|
||||||
|
// so if something is terribly wrong, please check git diff PodCommsSession.swift since that commit
|
||||||
|
r = updateRegister(CC111XRegister.pktctrl1, 0x20);
|
||||||
|
r = updateRegister(CC111XRegister.agcctrl0, 0x00);
|
||||||
|
r = updateRegister(CC111XRegister.fsctrl1, 0x06);
|
||||||
|
r = updateRegister(CC111XRegister.mdmcfg4, 0xCA);
|
||||||
|
r = updateRegister(CC111XRegister.mdmcfg3, 0xBC);
|
||||||
|
r = updateRegister(CC111XRegister.mdmcfg2, 0x06);
|
||||||
|
r = updateRegister(CC111XRegister.mdmcfg1, 0x70);
|
||||||
|
r = updateRegister(CC111XRegister.mdmcfg0, 0x11);
|
||||||
|
r = updateRegister(CC111XRegister.deviatn, 0x44);
|
||||||
|
r = updateRegister(CC111XRegister.mcsm0, 0x18);
|
||||||
|
r = updateRegister(CC111XRegister.foccfg, 0x17);
|
||||||
|
r = updateRegister(CC111XRegister.fscal3, 0xE9);
|
||||||
|
r = updateRegister(CC111XRegister.fscal2, 0x2A);
|
||||||
|
r = updateRegister(CC111XRegister.fscal1, 0x00);
|
||||||
|
r = updateRegister(CC111XRegister.fscal0, 0x1F);
|
||||||
|
|
||||||
|
r = updateRegister(CC111XRegister.test1, 0x31);
|
||||||
|
r = updateRegister(CC111XRegister.test0, 0x09);
|
||||||
|
r = updateRegister(CC111XRegister.paTable0, 0x84);
|
||||||
|
r = updateRegister(CC111XRegister.sync1, 0xA5);
|
||||||
|
r = updateRegister(CC111XRegister.sync0, 0x5A);
|
||||||
|
|
||||||
|
r = setRileyLinkEncoding(RileyLinkEncodingType.Manchester);
|
||||||
|
r = setPreamble(0x6665);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("No region configuration for RfSpy and {}", frequency.name());
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedTargetFrequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setMedtronicEncoding() {
|
||||||
|
RileyLinkEncodingType encoding = RileyLinkEncodingType.FourByteSixByteLocal;
|
||||||
|
|
||||||
|
if (RileyLinkFirmwareVersion.isSameVersion(this.firmwareVersion, RileyLinkFirmwareVersion.Version2AndHigher)) {
|
||||||
|
if (SP.getString(MedtronicConst.Prefs.Encoding, "None").equals(MainApp.gs(R.string.medtronic_pump_encoding_4b6b_rileylink))) {
|
||||||
|
encoding = RileyLinkEncodingType.FourByteSixByteRileyLink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRileyLinkEncoding(encoding);
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Set Encoding for Medtronic: " + encoding.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private RFSpyResponse setPreamble(int preamble) {
|
||||||
|
RFSpyResponse resp = null;
|
||||||
|
try {
|
||||||
|
resp = writeToData(new SetPreamble(preamble), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.toString();
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse setRileyLinkEncoding(RileyLinkEncodingType encoding) {
|
||||||
|
RFSpyResponse resp = writeToData(new SetHardwareEncoding(encoding), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||||
|
|
||||||
|
if (resp.isOK()) {
|
||||||
|
reader.setRileyLinkEncodingType(encoding);
|
||||||
|
RileyLinkUtil.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setRXFilterMode(RXFilterMode mode) {
|
||||||
|
|
||||||
|
byte drate_e = (byte) 0x9; // exponent of symbol rate (16kbps)
|
||||||
|
byte chanbw = mode.value;
|
||||||
|
|
||||||
|
updateRegister(CC111XRegister.mdmcfg4, (byte) (chanbw | drate_e));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset RileyLink Configuration (set all updateRegisters)
|
||||||
|
*/
|
||||||
|
public void resetRileyLinkConfiguration() {
|
||||||
|
if (this.currentFrequencyMHz != null)
|
||||||
|
this.setBaseFrequency(this.currentFrequencyMHz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse resetRileyLink() {
|
||||||
|
RFSpyResponse resp = null;
|
||||||
|
try {
|
||||||
|
resp = writeToData(new Reset(), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Reset command send, response: {}", resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.toString();
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPBTCOMM);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class RFSpyReader {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
private static AsyncTask<Void, Void, Void> readerTask;
|
||||||
|
private RileyLinkBLE rileyLinkBle;
|
||||||
|
private Semaphore waitForRadioData = new Semaphore(0, true);
|
||||||
|
private LinkedBlockingQueue<byte[]> mDataQueue = new LinkedBlockingQueue<>();
|
||||||
|
private int acquireCount = 0;
|
||||||
|
private int releaseCount = 0;
|
||||||
|
private boolean stopAtNull = true;
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyReader(RileyLinkBLE rileyLinkBle) {
|
||||||
|
this.rileyLinkBle = rileyLinkBle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init(RileyLinkBLE rileyLinkBLE) {
|
||||||
|
this.rileyLinkBle = rileyLinkBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) {
|
||||||
|
if (readerTask != null) {
|
||||||
|
readerTask.cancel(true);
|
||||||
|
}
|
||||||
|
this.rileyLinkBle = rileyLinkBle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRileyLinkEncodingType(RileyLinkEncodingType encodingType) {
|
||||||
|
stopAtNull = !(encodingType == RileyLinkEncodingType.Manchester || //
|
||||||
|
encodingType == RileyLinkEncodingType.FourByteSixByteRileyLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen.
|
||||||
|
public byte[] poll(int timeout_ms) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace(ThreadUtil.sig() + "Entering poll at t==" + SystemClock.uptimeMillis() + ", timeout is " + timeout_ms
|
||||||
|
+ " mDataQueue size is " + mDataQueue.size());
|
||||||
|
|
||||||
|
if (mDataQueue.isEmpty())
|
||||||
|
try {
|
||||||
|
// block until timeout or data available.
|
||||||
|
// returns null if timeout.
|
||||||
|
byte[] dataFromQueue = mDataQueue.poll(timeout_ms, TimeUnit.MILLISECONDS);
|
||||||
|
if (dataFromQueue != null) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t=="
|
||||||
|
+ SystemClock.uptimeMillis());
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Got data [null] at t==" + SystemClock.uptimeMillis());
|
||||||
|
}
|
||||||
|
return dataFromQueue;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("poll: Interrupted waiting for data");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Call this from the "response count" notification handler.
|
||||||
|
public void newDataIsAvailable() {
|
||||||
|
releaseCount++;
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace(ThreadUtil.sig() + "waitForRadioData released(count=" + releaseCount + ") at t="
|
||||||
|
+ SystemClock.uptimeMillis());
|
||||||
|
waitForRadioData.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
readerTask = new AsyncTask<Void, Void, Void>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
UUID serviceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
|
||||||
|
UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
|
||||||
|
BLECommOperationResult result;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
acquireCount++;
|
||||||
|
waitForRadioData.acquire();
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.trace(ThreadUtil.sig() + "waitForRadioData acquired (count=" + acquireCount + ") at t="
|
||||||
|
+ SystemClock.uptimeMillis());
|
||||||
|
SystemClock.sleep(100);
|
||||||
|
SystemClock.sleep(1);
|
||||||
|
result = rileyLinkBle.readCharacteristic_blocking(serviceUUID, radioDataUUID);
|
||||||
|
SystemClock.sleep(100);
|
||||||
|
|
||||||
|
if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
|
||||||
|
if (stopAtNull) {
|
||||||
|
// only data up to the first null is valid
|
||||||
|
for (int i = 0; i < result.value.length; i++) {
|
||||||
|
if (result.value[i] == 0) {
|
||||||
|
result.value = ByteUtil.substring(result.value, 0, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mDataQueue.add(result.value);
|
||||||
|
} else if (result.resultCode == BLECommOperationResult.RESULT_INTERRUPTED) {
|
||||||
|
LOG.error("Read operation was interrupted");
|
||||||
|
} else if (result.resultCode == BLECommOperationResult.RESULT_TIMEOUT) {
|
||||||
|
LOG.error("Read operation on Radio Data timed out");
|
||||||
|
} else if (result.resultCode == BLECommOperationResult.RESULT_BUSY) {
|
||||||
|
LOG.error("FAIL: RileyLinkBLE reports operation already in progress");
|
||||||
|
} else if (result.resultCode == BLECommOperationResult.RESULT_NONE) {
|
||||||
|
LOG.error("FAIL: got invalid result code: " + result.resultCode);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("Interrupted while waiting for data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPBTCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,572 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||||
|
|
||||||
|
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.BluetoothProfile;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperation;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicReadOperation;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicWriteOperation;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.DescriptorWriteOperation;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
* Added: State handling, configuration of RF for different configuration ranges, connection handling
|
||||||
|
*/
|
||||||
|
public class RileyLinkBLE {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
public boolean gattDebugEnabled = true;
|
||||||
|
boolean manualDisconnect = false;
|
||||||
|
private BluetoothAdapter bluetoothAdapter;
|
||||||
|
private BluetoothGattCallback bluetoothGattCallback;
|
||||||
|
private BluetoothDevice rileyLinkDevice;
|
||||||
|
private BluetoothGatt bluetoothConnectionGatt = null;
|
||||||
|
private BLECommOperation mCurrentOperation;
|
||||||
|
private Semaphore gattOperationSema = new Semaphore(1, true);
|
||||||
|
private Runnable radioResponseCountNotified;
|
||||||
|
private boolean mIsConnected = false;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkBLE(final Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("BT Adapter: " + this.bluetoothAdapter);
|
||||||
|
bluetoothGattCallback = new BluetoothGattCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCharacteristicChanged(final BluetoothGatt gatt,
|
||||||
|
final BluetoothGattCharacteristic characteristic) {
|
||||||
|
super.onCharacteristicChanged(gatt, characteristic);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.trace(ThreadUtil.sig() + "onCharacteristicChanged "
|
||||||
|
+ GattAttributes.lookup(characteristic.getUuid()) + " "
|
||||||
|
+ ByteUtil.getHex(characteristic.getValue()));
|
||||||
|
if (characteristic.getUuid().equals(UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT))) {
|
||||||
|
LOG.debug("Response Count is " + ByteUtil.shortHexString(characteristic.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (radioResponseCountNotified != null) {
|
||||||
|
radioResponseCountNotified.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCharacteristicRead(final BluetoothGatt gatt,
|
||||||
|
final BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
super.onCharacteristicRead(gatt, characteristic, status);
|
||||||
|
|
||||||
|
final String statusMessage = getGattStatusMessage(status);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.trace(ThreadUtil.sig() + "onCharacteristicRead ("
|
||||||
|
+ GattAttributes.lookup(characteristic.getUuid()) + ") " + statusMessage + ":"
|
||||||
|
+ ByteUtil.getHex(characteristic.getValue()));
|
||||||
|
}
|
||||||
|
mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCharacteristicWrite(final BluetoothGatt gatt,
|
||||||
|
final BluetoothGattCharacteristic characteristic, int status) {
|
||||||
|
super.onCharacteristicWrite(gatt, characteristic, status);
|
||||||
|
|
||||||
|
final String uuidString = GattAttributes.lookup(characteristic.getUuid());
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.trace(ThreadUtil.sig() + "onCharacteristicWrite " + getGattStatusMessage(status) + " "
|
||||||
|
+ uuidString + " " + ByteUtil.shortHexString(characteristic.getValue()));
|
||||||
|
}
|
||||||
|
mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
|
||||||
|
super.onConnectionStateChange(gatt, status, newState);
|
||||||
|
|
||||||
|
// https://github.com/NordicSemiconductor/puck-central-android/blob/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/GattManager.java#L117
|
||||||
|
if (status == 133) {
|
||||||
|
LOG.error("Got the status 133 bug, closing gatt");
|
||||||
|
disconnect();
|
||||||
|
SystemClock.sleep(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gattDebugEnabled) {
|
||||||
|
final String stateMessage;
|
||||||
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
stateMessage = "CONNECTED";
|
||||||
|
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
|
||||||
|
stateMessage = "CONNECTING";
|
||||||
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
|
stateMessage = "DISCONNECTED";
|
||||||
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
|
||||||
|
stateMessage = "DISCONNECTING";
|
||||||
|
} else {
|
||||||
|
stateMessage = "UNKNOWN newState (" + newState + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.warn("onConnectionStateChange " + getGattStatusMessage(status) + " " + stateMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothConnected);
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("BT State connected, GATT status {} ({})", status, getGattStatusMessage(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ((newState == BluetoothProfile.STATE_CONNECTING) || //
|
||||||
|
(newState == BluetoothProfile.STATE_DISCONNECTING)) {
|
||||||
|
// LOG.debug("We are in {} state.", status == BluetoothProfile.STATE_CONNECTING ? "Connecting" :
|
||||||
|
// "Disconnecting");
|
||||||
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnected);
|
||||||
|
if (manualDisconnect)
|
||||||
|
close();
|
||||||
|
LOG.warn("RileyLink Disconnected.");
|
||||||
|
} else {
|
||||||
|
LOG.warn("Some other state: (status={},newState={})", status, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
super.onDescriptorWrite(gatt, descriptor, status);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onDescriptorWrite " + GattAttributes.lookup(descriptor.getUuid()) + " "
|
||||||
|
+ getGattStatusMessage(status) + " written: " + ByteUtil.getHex(descriptor.getValue()));
|
||||||
|
}
|
||||||
|
mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||||
|
super.onDescriptorRead(gatt, descriptor, status);
|
||||||
|
mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue());
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onDescriptorRead " + getGattStatusMessage(status) + " status " + descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||||
|
super.onMtuChanged(gatt, mtu, status);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onMtuChanged " + mtu + " status " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReadRemoteRssi(final BluetoothGatt gatt, int rssi, int status) {
|
||||||
|
super.onReadRemoteRssi(gatt, rssi, status);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onReadRemoteRssi " + getGattStatusMessage(status) + ": " + rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||||
|
super.onReliableWriteCompleted(gatt, status);
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onReliableWriteCompleted status " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||||
|
super.onServicesDiscovered(gatt, status);
|
||||||
|
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
final List<BluetoothGattService> services = gatt.getServices();
|
||||||
|
|
||||||
|
boolean rileyLinkFound = false;
|
||||||
|
|
||||||
|
for (BluetoothGattService service : services) {
|
||||||
|
final UUID uuidService = service.getUuid();
|
||||||
|
|
||||||
|
if (isAnyRileyLinkServiceFound(service)) {
|
||||||
|
rileyLinkFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gattDebugEnabled) {
|
||||||
|
debugService(service, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gattDebugEnabled && isLogEnabled()) {
|
||||||
|
LOG.warn("onServicesDiscovered " + getGattStatusMessage(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Gatt device is RileyLink device: " + rileyLinkFound);
|
||||||
|
|
||||||
|
if (rileyLinkFound) {
|
||||||
|
mIsConnected = true;
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkReady);
|
||||||
|
// RileyLinkUtil.sendNotification(new
|
||||||
|
// ServiceNotification(RileyLinkConst.Intents.RileyLinkReady), null);
|
||||||
|
} else {
|
||||||
|
mIsConnected = false;
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkError,
|
||||||
|
RileyLinkError.DeviceIsNotRileyLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("onServicesDiscovered " + getGattStatusMessage(status));
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkGattFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isAnyRileyLinkServiceFound(BluetoothGattService service) {
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
found = GattAttributes.isRileyLink(service.getUuid());
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
List<BluetoothGattService> includedServices = service.getIncludedServices();
|
||||||
|
|
||||||
|
for (BluetoothGattService serviceI : includedServices) {
|
||||||
|
if (isAnyRileyLinkServiceFound(serviceI)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BluetoothDevice getRileyLinkDevice() {
|
||||||
|
return this.rileyLinkDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void debugService(BluetoothGattService service, int indentCount) {
|
||||||
|
|
||||||
|
String indentString = StringUtils.repeat(' ', indentCount);
|
||||||
|
|
||||||
|
final UUID uuidService = service.getUuid();
|
||||||
|
|
||||||
|
if (gattDebugEnabled) {
|
||||||
|
final String uuidServiceString = uuidService.toString();
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
stringBuilder.append(indentString);
|
||||||
|
stringBuilder.append(GattAttributes.lookup(uuidServiceString, "Unknown service"));
|
||||||
|
stringBuilder.append(" (" + uuidServiceString + ")");
|
||||||
|
|
||||||
|
for (BluetoothGattCharacteristic character : service.getCharacteristics()) {
|
||||||
|
final String uuidCharacteristicString = character.getUuid().toString();
|
||||||
|
|
||||||
|
stringBuilder.append("\n ");
|
||||||
|
stringBuilder.append(indentString);
|
||||||
|
stringBuilder.append(" - " + GattAttributes.lookup(uuidCharacteristicString, "Unknown Characteristic"));
|
||||||
|
stringBuilder.append(" (" + uuidCharacteristicString + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder.append("\n\n");
|
||||||
|
|
||||||
|
LOG.warn(stringBuilder.toString());
|
||||||
|
|
||||||
|
List<BluetoothGattService> includedServices = service.getIncludedServices();
|
||||||
|
|
||||||
|
for (BluetoothGattService serviceI : includedServices) {
|
||||||
|
debugService(serviceI, indentCount + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void registerRadioResponseCountNotification(Runnable notifier) {
|
||||||
|
radioResponseCountNotified = notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return mIsConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean discoverServices() {
|
||||||
|
|
||||||
|
if (bluetoothConnectionGatt == null) {
|
||||||
|
// shouldn't happen, but if it does we exit
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bluetoothConnectionGatt.discoverServices()) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.warn("Starting to discover GATT Services.");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG.error("Cannot discover GATT Services.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean enableNotifications() {
|
||||||
|
BLECommOperationResult result = setNotification_blocking(UUID.fromString(GattAttributes.SERVICE_RADIO), //
|
||||||
|
UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT));
|
||||||
|
if (result.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
|
||||||
|
LOG.error("Error setting response count notification");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void findRileyLink(String RileyLinkAddress) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("RileyLink address: " + RileyLinkAddress);
|
||||||
|
// Must verify that this is a valid MAC, or crash.
|
||||||
|
|
||||||
|
rileyLinkDevice = bluetoothAdapter.getRemoteDevice(RileyLinkAddress);
|
||||||
|
// if this succeeds, we get a connection state change callback?
|
||||||
|
connectGatt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This function must be run on UI thread.
|
||||||
|
public void connectGatt() {
|
||||||
|
bluetoothConnectionGatt = rileyLinkDevice.connectGatt(context, true, bluetoothGattCallback);
|
||||||
|
// , BluetoothDevice.TRANSPORT_LE
|
||||||
|
if (bluetoothConnectionGatt == null) {
|
||||||
|
LOG.error("Failed to connect to Bluetooth Low Energy device at " + bluetoothAdapter.getAddress());
|
||||||
|
} else {
|
||||||
|
if (gattDebugEnabled) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Gatt Connected?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
mIsConnected = false;
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.warn("Closing GATT connection");
|
||||||
|
// Close old conenction
|
||||||
|
if (bluetoothConnectionGatt != null) {
|
||||||
|
// Not sure if to disconnect or to close first..
|
||||||
|
bluetoothConnectionGatt.disconnect();
|
||||||
|
manualDisconnect = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (bluetoothConnectionGatt != null) {
|
||||||
|
bluetoothConnectionGatt.close();
|
||||||
|
bluetoothConnectionGatt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BLECommOperationResult setNotification_blocking(UUID serviceUUID, UUID charaUUID) {
|
||||||
|
BLECommOperationResult rval = new BLECommOperationResult();
|
||||||
|
if (bluetoothConnectionGatt != null) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
gattOperationSema.acquire();
|
||||||
|
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("setNotification_blocking: interrupted waiting for gattOperationSema");
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
if (mCurrentOperation != null) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||||
|
} else {
|
||||||
|
if (bluetoothConnectionGatt.getService(serviceUUID) == null) {
|
||||||
|
// Catch if the service is not supported by the BLE device
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_NONE;
|
||||||
|
LOG.error("BT Device not supported");
|
||||||
|
// TODO: 11/07/2016 UI update for user
|
||||||
|
} else {
|
||||||
|
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID)
|
||||||
|
.getCharacteristic(charaUUID);
|
||||||
|
// Tell Android that we want the notifications
|
||||||
|
bluetoothConnectionGatt.setCharacteristicNotification(chara, true);
|
||||||
|
List<BluetoothGattDescriptor> list = chara.getDescriptors();
|
||||||
|
if (gattDebugEnabled) {
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Found descriptor: " + list.get(i).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BluetoothGattDescriptor descr = list.get(0);
|
||||||
|
// Tell the remote device to send the notifications
|
||||||
|
mCurrentOperation = new DescriptorWriteOperation(bluetoothConnectionGatt, descr,
|
||||||
|
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||||
|
mCurrentOperation.execute(this);
|
||||||
|
if (mCurrentOperation.timedOut) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||||
|
} else if (mCurrentOperation.interrupted) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||||
|
} else {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCurrentOperation = null;
|
||||||
|
gattOperationSema.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("setNotification_blocking: not configured!");
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// call from main
|
||||||
|
public BLECommOperationResult writeCharacteristic_blocking(UUID serviceUUID, UUID charaUUID, byte[] value) {
|
||||||
|
BLECommOperationResult rval = new BLECommOperationResult();
|
||||||
|
if (bluetoothConnectionGatt != null) {
|
||||||
|
rval.value = value;
|
||||||
|
try {
|
||||||
|
gattOperationSema.acquire();
|
||||||
|
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("writeCharacteristic_blocking: interrupted waiting for gattOperationSema");
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCurrentOperation != null) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||||
|
} else {
|
||||||
|
if (bluetoothConnectionGatt.getService(serviceUUID) == null) {
|
||||||
|
// Catch if the service is not supported by the BLE device
|
||||||
|
// GGW: Tue Jul 12 01:14:01 UTC 2016: This can also happen if the
|
||||||
|
// app that created the bluetoothConnectionGatt has been destroyed/created,
|
||||||
|
// e.g. when the user switches from portrait to landscape.
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_NONE;
|
||||||
|
LOG.error("BT Device not supported");
|
||||||
|
// TODO: 11/07/2016 UI update for user
|
||||||
|
} else {
|
||||||
|
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID)
|
||||||
|
.getCharacteristic(charaUUID);
|
||||||
|
mCurrentOperation = new CharacteristicWriteOperation(bluetoothConnectionGatt, chara, value);
|
||||||
|
mCurrentOperation.execute(this);
|
||||||
|
if (mCurrentOperation.timedOut) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||||
|
} else if (mCurrentOperation.interrupted) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||||
|
} else {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCurrentOperation = null;
|
||||||
|
gattOperationSema.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("writeCharacteristic_blocking: not configured!");
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BLECommOperationResult readCharacteristic_blocking(UUID serviceUUID, UUID charaUUID) {
|
||||||
|
BLECommOperationResult rval = new BLECommOperationResult();
|
||||||
|
if (bluetoothConnectionGatt != null) {
|
||||||
|
try {
|
||||||
|
gattOperationSema.acquire();
|
||||||
|
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("readCharacteristic_blocking: Interrupted waiting for gattOperationSema");
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
if (mCurrentOperation != null) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||||
|
} else {
|
||||||
|
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic(
|
||||||
|
charaUUID);
|
||||||
|
mCurrentOperation = new CharacteristicReadOperation(bluetoothConnectionGatt, chara);
|
||||||
|
mCurrentOperation.execute(this);
|
||||||
|
if (mCurrentOperation.timedOut) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||||
|
} else if (mCurrentOperation.interrupted) {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||||
|
} else {
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||||
|
rval.value = mCurrentOperation.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCurrentOperation = null;
|
||||||
|
gattOperationSema.release();
|
||||||
|
} else {
|
||||||
|
LOG.error("readCharacteristic_blocking: not configured!");
|
||||||
|
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getGattStatusMessage(final int status) {
|
||||||
|
final String statusMessage;
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
statusMessage = "SUCCESS";
|
||||||
|
} else if (status == BluetoothGatt.GATT_FAILURE) {
|
||||||
|
statusMessage = "FAILED";
|
||||||
|
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
|
||||||
|
statusMessage = "NOT PERMITTED";
|
||||||
|
} else if (status == 133) {
|
||||||
|
statusMessage = "Found the strange 133 bug";
|
||||||
|
} else {
|
||||||
|
statusMessage = "UNKNOWN (" + status + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPBTCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/23/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkCommunicationException extends Exception {
|
||||||
|
|
||||||
|
String extendedErrorText;
|
||||||
|
private RileyLinkBLEError errorCode;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkCommunicationException(RileyLinkBLEError errorCode, String extendedErrorText) {
|
||||||
|
super(errorCode.getDescription());
|
||||||
|
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.extendedErrorText = extendedErrorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkCommunicationException(RileyLinkBLEError errorCode) {
|
||||||
|
super(errorCode.getDescription());
|
||||||
|
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
// this.extendedErrorText = extendedErrorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
|
||||||
|
public class GetVersion extends RileyLinkCommand {
|
||||||
|
|
||||||
|
public GetVersion() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.GetVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return super.getRawSimple();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
|
||||||
|
public class Reset extends RileyLinkCommand {
|
||||||
|
|
||||||
|
public Reset() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.Reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return super.getRawSimple();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
|
||||||
|
public class ResetRadioConfig extends RileyLinkCommand {
|
||||||
|
|
||||||
|
public ResetRadioConfig() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.ResetRadioConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return super.getRawSimple();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
|
||||||
|
public abstract class RileyLinkCommand {
|
||||||
|
|
||||||
|
public RileyLinkCommand() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract RileyLinkCommandType getCommandType();
|
||||||
|
|
||||||
|
|
||||||
|
public abstract byte[] getRaw();
|
||||||
|
|
||||||
|
|
||||||
|
protected byte[] getRawSimple() {
|
||||||
|
return getByteArray(getCommandType().code);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected byte[] getByteArray(byte... input) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
|
||||||
|
public class SendAndListen extends RileyLinkCommand {
|
||||||
|
|
||||||
|
private byte sendChannel;
|
||||||
|
private byte repeatCount;
|
||||||
|
private int delayBetweenPackets_ms;
|
||||||
|
private byte listenChannel;
|
||||||
|
private int timeout_ms;
|
||||||
|
private byte retryCount;
|
||||||
|
private Integer preambleExtension_ms;
|
||||||
|
private RadioPacket packetToSend;
|
||||||
|
|
||||||
|
|
||||||
|
public SendAndListen(byte sendChannel, byte repeatCount, byte delayBetweenPackets_ms, byte listenChannel,
|
||||||
|
int timeout_ms, byte retryCount, RadioPacket packetToSend
|
||||||
|
|
||||||
|
) {
|
||||||
|
this(sendChannel, repeatCount, delayBetweenPackets_ms, listenChannel, timeout_ms, retryCount, null,
|
||||||
|
packetToSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SendAndListen(byte sendChannel, byte repeatCount, int delayBetweenPackets_ms, byte listenChannel,
|
||||||
|
int timeout_ms, byte retryCount, Integer preambleExtension_ms, RadioPacket packetToSend
|
||||||
|
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.sendChannel = sendChannel;
|
||||||
|
this.repeatCount = repeatCount;
|
||||||
|
this.delayBetweenPackets_ms = delayBetweenPackets_ms;
|
||||||
|
this.listenChannel = listenChannel;
|
||||||
|
this.timeout_ms = timeout_ms;
|
||||||
|
this.retryCount = retryCount;
|
||||||
|
this.preambleExtension_ms = preambleExtension_ms == null ? 0 : preambleExtension_ms;
|
||||||
|
this.packetToSend = packetToSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.SendAndListen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
|
||||||
|
// If firmware version is not set (error reading version from device, shouldn't happen),
|
||||||
|
// we will default to version 2
|
||||||
|
boolean isPacketV2 = RileyLinkUtil.getFirmwareVersion() != null ? RileyLinkUtil.getFirmwareVersion()
|
||||||
|
.isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher) : true;
|
||||||
|
|
||||||
|
ArrayList<Byte> bytes = new ArrayList<Byte>();
|
||||||
|
bytes.add(this.getCommandType().code);
|
||||||
|
bytes.add(this.sendChannel);
|
||||||
|
bytes.add(this.repeatCount);
|
||||||
|
|
||||||
|
if (isPacketV2) { // delay is unsigned 16-bit integer
|
||||||
|
byte[] delayBuff = ByteBuffer.allocate(4).putInt(delayBetweenPackets_ms).array();
|
||||||
|
bytes.add(delayBuff[2]);
|
||||||
|
bytes.add(delayBuff[3]);
|
||||||
|
} else {
|
||||||
|
bytes.add((byte)delayBetweenPackets_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.add(this.listenChannel);
|
||||||
|
|
||||||
|
byte[] timeoutBuff = ByteBuffer.allocate(4).putInt(timeout_ms).array();
|
||||||
|
|
||||||
|
bytes.add(timeoutBuff[0]);
|
||||||
|
bytes.add(timeoutBuff[1]);
|
||||||
|
bytes.add(timeoutBuff[2]);
|
||||||
|
bytes.add(timeoutBuff[3]);
|
||||||
|
|
||||||
|
bytes.add(retryCount);
|
||||||
|
|
||||||
|
if (isPacketV2) { // 2.x (and probably higher versions) support preamble extension
|
||||||
|
byte[] preambleBuf = ByteBuffer.allocate(4).putInt(preambleExtension_ms).array();
|
||||||
|
bytes.add(preambleBuf[2]);
|
||||||
|
bytes.add(preambleBuf[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteUtil.concat(ByteUtil.getByteArrayFromList(bytes), packetToSend.getEncoded());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||||
|
|
||||||
|
public class SetHardwareEncoding extends RileyLinkCommand {
|
||||||
|
|
||||||
|
private final RileyLinkEncodingType encoding;
|
||||||
|
|
||||||
|
|
||||||
|
public SetHardwareEncoding(RileyLinkEncodingType encoding) {
|
||||||
|
super();
|
||||||
|
this.encoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.SetHardwareEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return getByteArray(getCommandType().code, encoding.value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
|
||||||
|
public class SetPreamble extends RileyLinkCommand {
|
||||||
|
|
||||||
|
private int preamble;
|
||||||
|
|
||||||
|
|
||||||
|
public SetPreamble(int preamble) throws Exception {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// this command was not supported before 2.0
|
||||||
|
if (!RileyLinkUtil.getFirmwareVersion().isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher)) {
|
||||||
|
throw new NotImplementedException("Old firmware does not support SetPreamble command");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preamble < 0 || preamble > 0xFFFF) {
|
||||||
|
throw new Exception("preamble value is out of range");
|
||||||
|
}
|
||||||
|
this.preamble = preamble;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.SetPreamble;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
byte[] bytes = ByteBuffer.allocate(4).putInt(preamble).array();
|
||||||
|
return getByteArray(this.getCommandType().code, bytes[2], bytes[3]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
|
||||||
|
public class UpdateRegister extends RileyLinkCommand {
|
||||||
|
|
||||||
|
CC111XRegister register;
|
||||||
|
byte registerValue;
|
||||||
|
|
||||||
|
|
||||||
|
public UpdateRegister(CC111XRegister register, byte registerValue) {
|
||||||
|
super();
|
||||||
|
this.register = register;
|
||||||
|
this.registerValue = registerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RileyLinkCommandType getCommandType() {
|
||||||
|
return RileyLinkCommandType.UpdateRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return getByteArray(getCommandType().code, register.value, registerValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/30/16.
|
||||||
|
* changed by Andy 10/20/18
|
||||||
|
*/
|
||||||
|
public class FrequencyScanResults {
|
||||||
|
|
||||||
|
public List<FrequencyTrial> trials = new ArrayList<>();
|
||||||
|
public double bestFrequencyMHz = 0.0;
|
||||||
|
public long dateTime;
|
||||||
|
|
||||||
|
|
||||||
|
public void sort() {
|
||||||
|
Collections.sort(trials, (trial1, trial2) -> {
|
||||||
|
int res = trial1.averageRSSI.compareTo(trial2.averageRSSI);
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
return (int)(trial1.frequencyMHz - trial2.frequencyMHz);
|
||||||
|
} else
|
||||||
|
return res;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/30/16.
|
||||||
|
* changed by Andy 10/20/18
|
||||||
|
*/
|
||||||
|
public class FrequencyTrial {
|
||||||
|
|
||||||
|
public int tries = 0;
|
||||||
|
public int successes = 0;
|
||||||
|
public Double averageRSSI = 0.0;
|
||||||
|
public double frequencyMHz = 0.0;
|
||||||
|
public List<Integer> rssiList = new ArrayList<>();
|
||||||
|
public double averageRSSI2;
|
||||||
|
|
||||||
|
|
||||||
|
public void calculateAverage() {
|
||||||
|
int sum = 0;
|
||||||
|
int count = 0;
|
||||||
|
for (Integer rssi : rssiList) {
|
||||||
|
sum += Math.abs(rssi);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
double avg = (sum / (count * 1.0d));
|
||||||
|
|
||||||
|
if (count != 0)
|
||||||
|
this.averageRSSI = avg * (-1);
|
||||||
|
else
|
||||||
|
this.averageRSSI = -99.0d;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/21/16.
|
||||||
|
*/
|
||||||
|
public class GattAttributes {
|
||||||
|
|
||||||
|
// NOTE: these uuid strings must be lower case!
|
||||||
|
|
||||||
|
public static String PREFIX = "0000";
|
||||||
|
public static String SUFFIX = "-0000-1000-8000-00805f9b34fb";
|
||||||
|
public static String SERVICE_GAP = PREFIX + "1800" + SUFFIX;
|
||||||
|
public static String CHARA_GAP_NAME = PREFIX + "2a00" + SUFFIX; // RileyLink RFSpy
|
||||||
|
public static String CHARA_GAP_NUM = PREFIX + "2a01" + SUFFIX; // 0000
|
||||||
|
public static String CHARA_GAP_UNK = PREFIX + "2a01" + SUFFIX; // a
|
||||||
|
|
||||||
|
public static String SERVICE_BATTERY = PREFIX + "180f" + SUFFIX; // Battery
|
||||||
|
public static String CHARA_BATTERY_UNK = PREFIX + "2a19" + SUFFIX;
|
||||||
|
|
||||||
|
// RileyLink Radio Service
|
||||||
|
public static String SERVICE_RADIO = "0235733b-99c5-4197-b856-69219c2a3845";
|
||||||
|
public static String CHARA_RADIO_DATA = "c842e849-5028-42e2-867c-016adada9155";
|
||||||
|
public static String CHARA_RADIO_RESPONSE_COUNT = "6e6c7910-b89e-43a5-a0fe-50c5e2b81f4a";
|
||||||
|
public static String CHARA_RADIO_TIMER_TICK = "6e6c7910-b89e-43a5-78af-50c5e2b86f7e";
|
||||||
|
public static String CHARA_RADIO_CUSTOM_NAME = "d93b2af0-1e28-11e4-8c21-0800200c9a66";
|
||||||
|
public static String CHARA_RADIO_VERSION = "30d99dc9-7c91-4295-a051-0a104d238cf2";
|
||||||
|
public static String CHARA_RADIO_LED_MODE = "c6d84241-f1a7-4f9c-a25f-fce16732f14e";
|
||||||
|
|
||||||
|
private static Map<String, String> attributes;
|
||||||
|
private static Map<String, String> attributesRileyLinkSpecific;
|
||||||
|
|
||||||
|
// table of names for uuids
|
||||||
|
static {
|
||||||
|
attributes = new HashMap<>();
|
||||||
|
|
||||||
|
attributes.put(SERVICE_GAP, "Device Information Service");
|
||||||
|
attributes.put(CHARA_GAP_NAME, "Name"); //
|
||||||
|
attributes.put(CHARA_GAP_NUM, "Number"); //
|
||||||
|
|
||||||
|
attributes.put(SERVICE_BATTERY, "Battery Service");
|
||||||
|
|
||||||
|
attributes.put(SERVICE_RADIO, "Radio Interface"); // a
|
||||||
|
attributes.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name");
|
||||||
|
attributes.put(CHARA_RADIO_DATA, "Data");
|
||||||
|
attributes.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count");
|
||||||
|
attributes.put(CHARA_RADIO_TIMER_TICK, "Timer Tick");
|
||||||
|
attributes.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion
|
||||||
|
attributes.put(CHARA_RADIO_LED_MODE, "Led Mode");
|
||||||
|
|
||||||
|
attributesRileyLinkSpecific = new HashMap<>();
|
||||||
|
|
||||||
|
attributesRileyLinkSpecific.put(SERVICE_RADIO, "Radio Interface"); // a
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name");
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_DATA, "Data");
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count");
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_TIMER_TICK, "Timer Tick");
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion
|
||||||
|
attributesRileyLinkSpecific.put(CHARA_RADIO_LED_MODE, "Led Mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String lookup(UUID uuid) {
|
||||||
|
return lookup(uuid.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String lookup(String uuid) {
|
||||||
|
return lookup(uuid, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String lookup(String uuid, String defaultName) {
|
||||||
|
String name = attributes.get(uuid);
|
||||||
|
return name == null ? defaultName : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// we check for specific UUID (Radio ones, because thoose seem to be unique
|
||||||
|
public static boolean isRileyLink(UUID uuid) {
|
||||||
|
return attributesRileyLinkSpecific.containsKey(uuid.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RFSpyRLResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class RFSpyResponse {
|
||||||
|
|
||||||
|
// 0xaa == timeout
|
||||||
|
// 0xbb == interrupted
|
||||||
|
// 0xcc == zero-data
|
||||||
|
// 0xdd == success
|
||||||
|
// 0x11 == invalidParam
|
||||||
|
// 0x22 == unknownCommand
|
||||||
|
|
||||||
|
protected byte[] raw;
|
||||||
|
protected RadioResponse radioResponse;
|
||||||
|
private RileyLinkCommand command;
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse() {
|
||||||
|
init(new byte[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse(byte[] bytes) {
|
||||||
|
init(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RFSpyResponse(RileyLinkCommand command, byte[] rawResponse) {
|
||||||
|
|
||||||
|
this.command = command;
|
||||||
|
init(rawResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init(byte[] bytes) {
|
||||||
|
if (bytes == null) {
|
||||||
|
raw = new byte[0];
|
||||||
|
} else {
|
||||||
|
raw = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RadioResponse getRadioResponse() throws RileyLinkCommunicationException {
|
||||||
|
if (looksLikeRadioPacket()) {
|
||||||
|
radioResponse = new RadioResponse(command);
|
||||||
|
radioResponse.init(raw);
|
||||||
|
} else {
|
||||||
|
radioResponse = new RadioResponse();
|
||||||
|
}
|
||||||
|
return radioResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean wasTimeout() {
|
||||||
|
if ((raw.length == 1) || (raw.length == 2)) {
|
||||||
|
if (raw[0] == (byte)0xaa) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean wasInterrupted() {
|
||||||
|
if ((raw.length == 1) || (raw.length == 2)) {
|
||||||
|
if (raw[0] == (byte)0xbb) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isInvalidParam() {
|
||||||
|
if ((raw.length == 1) || (raw.length == 2)) {
|
||||||
|
if (raw[0] == (byte)0x11) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isUnknownCommand() {
|
||||||
|
if ((raw.length == 1) || (raw.length == 2)) {
|
||||||
|
if (raw[0] == (byte)0x22) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isOK() {
|
||||||
|
if ((raw.length == 1) || (raw.length == 2)) {
|
||||||
|
if (raw[0] == (byte)0x01 || raw[0] == (byte)0xDD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean looksLikeRadioPacket() {
|
||||||
|
if (raw.length > 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (raw.length > 2) {
|
||||||
|
return "Radio packet";
|
||||||
|
} else {
|
||||||
|
RFSpyRLResponse r = RFSpyRLResponse.fromByte(raw[0]);
|
||||||
|
return r.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/6/18.
|
||||||
|
*/
|
||||||
|
public interface RLMessage {
|
||||||
|
|
||||||
|
byte[] getTxData();
|
||||||
|
|
||||||
|
|
||||||
|
boolean isValid();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/6/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RLMessageType {
|
||||||
|
PowerOn, // for powering on the pump (wakeup)
|
||||||
|
ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel)
|
||||||
|
;
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.CRC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/22/16.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RadioPacket {
|
||||||
|
|
||||||
|
protected byte[] pkt;
|
||||||
|
|
||||||
|
|
||||||
|
public RadioPacket(byte[] pkt) {
|
||||||
|
this.pkt = pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getRaw() {
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getWithCRC() {
|
||||||
|
byte[] withCRC = ByteUtil.concat(pkt, CRC.crc8(pkt));
|
||||||
|
return withCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
|
||||||
|
switch (RileyLinkUtil.getEncoding()) {
|
||||||
|
case Manchester: { // We have this encoding in RL firmware
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FourByteSixByteLocal: {
|
||||||
|
byte[] withCRC = getWithCRC();
|
||||||
|
|
||||||
|
byte[] encoded = RileyLinkUtil.getEncoding4b6b().encode4b6b(withCRC);
|
||||||
|
return ByteUtil.concat(encoded, (byte)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
case FourByteSixByteRileyLink: {
|
||||||
|
return getWithCRC();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException(("Encoding not supported: " + RileyLinkUtil.getEncoding().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.CRC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/30/16.
|
||||||
|
*/
|
||||||
|
public class RadioResponse {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
|
||||||
|
public boolean decodedOK = false;
|
||||||
|
public int rssi;
|
||||||
|
public int responseNumber;
|
||||||
|
public byte[] decodedPayload = new byte[0];
|
||||||
|
public byte receivedCRC;
|
||||||
|
private RileyLinkCommand command;
|
||||||
|
|
||||||
|
|
||||||
|
public RadioResponse() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public RadioResponse(byte[] rxData) {
|
||||||
|
// init(rxData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public RadioResponse(RileyLinkCommand command /* , byte[] raw */) {
|
||||||
|
this.command = command;
|
||||||
|
// init(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
|
||||||
|
// We should check for all listening commands, but only one is actually used
|
||||||
|
if (command != null && command.getCommandType() != RileyLinkCommandType.SendAndListen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decodedOK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decodedPayload != null) {
|
||||||
|
if (receivedCRC == CRC.crc8(decodedPayload)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init(byte[] rxData) throws RileyLinkCommunicationException {
|
||||||
|
|
||||||
|
if (rxData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rxData.length < 3) {
|
||||||
|
// This does not look like something valid heard from a RileyLink device
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] encodedPayload;
|
||||||
|
|
||||||
|
if (RileyLinkFirmwareVersion.isSameVersion(RileyLinkUtil.getRileyLinkServiceData().versionCC110,
|
||||||
|
RileyLinkFirmwareVersion.Version2)) {
|
||||||
|
encodedPayload = ByteUtil.substring(rxData, 3, rxData.length - 3);
|
||||||
|
rssi = rxData[1];
|
||||||
|
responseNumber = rxData[2];
|
||||||
|
} else {
|
||||||
|
encodedPayload = ByteUtil.substring(rxData, 2, rxData.length - 2);
|
||||||
|
rssi = rxData[0];
|
||||||
|
responseNumber = rxData[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// for non-radio commands we just return the raw response
|
||||||
|
// well, for non-radio commands we shouldn't even reach this point
|
||||||
|
// but getVersion is kind of exception
|
||||||
|
if (command != null && //
|
||||||
|
command.getCommandType() != RileyLinkCommandType.SendAndListen) {
|
||||||
|
decodedOK = true;
|
||||||
|
decodedPayload = encodedPayload;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (RileyLinkUtil.getEncoding()) {
|
||||||
|
|
||||||
|
case Manchester:
|
||||||
|
case FourByteSixByteRileyLink: {
|
||||||
|
decodedOK = true;
|
||||||
|
decodedPayload = encodedPayload;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FourByteSixByteLocal: {
|
||||||
|
byte[] decodeThis = RileyLinkUtil.getEncoding4b6b().decode4b6b(encodedPayload);
|
||||||
|
|
||||||
|
if (decodeThis != null && decodeThis.length > 2) {
|
||||||
|
decodedOK = true;
|
||||||
|
|
||||||
|
decodedPayload = ByteUtil.substring(decodeThis, 0, decodeThis.length - 1);
|
||||||
|
receivedCRC = decodeThis[decodeThis.length - 1];
|
||||||
|
byte calculatedCRC = CRC.crc8(decodedPayload);
|
||||||
|
if (receivedCRC != calculatedCRC) {
|
||||||
|
LOG.error(String.format("RadioResponse: CRC mismatch, calculated 0x%02x, received 0x%02x",
|
||||||
|
calculatedCRC, receivedCRC));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new RileyLinkCommunicationException(RileyLinkBLEError.TooShortOrNullResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException("this {" + RileyLinkUtil.getEncoding().toString()
|
||||||
|
+ "} encoding is not supported");
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
decodedOK = false;
|
||||||
|
LOG.error("Failed to decode radio data: " + ByteUtil.shortHexString(encodedPayload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return decodedPayload;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface Encoding4b6b {
|
||||||
|
|
||||||
|
byte[] encode4b6b(byte[] data);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class Encoding4b6bAbstract implements Encoding4b6b {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encode4b6bMap is an ordered list of translations 6bits -> 4 bits, in order from 0x0 to 0xF
|
||||||
|
* The 6 bit codes are what is used on the RF side of the RileyLink to communicate
|
||||||
|
* with a Medtronic pump.
|
||||||
|
*/
|
||||||
|
public static final byte[] encode4b6bList = new byte[] {
|
||||||
|
0x15, 0x31, 0x32, 0x23, 0x34, 0x25, 0x26, 0x16, 0x1a, 0x19, 0x2a, 0x0b, 0x2c, 0x0d, 0x0e, 0x1c };
|
||||||
|
|
||||||
|
|
||||||
|
// 21, 49, 50, 35, 52, 37, 38, 22, 26, 25, 42, 11, 44, 13, 14, 28
|
||||||
|
|
||||||
|
public abstract byte[] encode4b6b(byte[] data);
|
||||||
|
|
||||||
|
|
||||||
|
public abstract byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException;
|
||||||
|
|
||||||
|
|
||||||
|
protected short convertUnsigned(byte x) {
|
||||||
|
short ss = x;
|
||||||
|
|
||||||
|
if (ss < 0) {
|
||||||
|
ss += 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* O(n) lookup. Run on an O(n) translation of a byte-stream, gives O(n**2) performance. Sigh. */
|
||||||
|
public static int encode4b6bListIndex(byte b) {
|
||||||
|
for (int i = 0; i < encode4b6bList.length; i++) {
|
||||||
|
if (b == encode4b6bList[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void writeError(Logger LOG, byte[] raw, String errorData) {
|
||||||
|
|
||||||
|
LOG.error("\n=============================================================================\n" + //
|
||||||
|
" Decoded payload length is zero.\n" +
|
||||||
|
" encodedPayload: {}\n" +
|
||||||
|
" errors: {}\n" +
|
||||||
|
"=============================================================================", //
|
||||||
|
ByteUtil.getHex(raw), errorData);
|
||||||
|
|
||||||
|
//FabricUtil.createEvent("MedtronicDecode4b6bError", null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Encoding4b6bGeoff extends Encoding4b6bAbstract {
|
||||||
|
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGeoff.class);
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] encode4b6b(byte[] data) {
|
||||||
|
// if ((data.length % 2) != 0) {
|
||||||
|
// LOG.error("Warning: data is odd number of bytes");
|
||||||
|
// }
|
||||||
|
// use arraylists because byte[] is annoying.
|
||||||
|
List<Byte> inData = ByteUtil.getListFromByteArray(data);
|
||||||
|
List<Byte> outData = new ArrayList<>();
|
||||||
|
|
||||||
|
int acc = 0;
|
||||||
|
int bitcount = 0;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < inData.size(); i++) {
|
||||||
|
acc <<= 6;
|
||||||
|
acc |= encode4b6bList[(inData.get(i) >> 4) & 0x0f];
|
||||||
|
bitcount += 6;
|
||||||
|
|
||||||
|
acc <<= 6;
|
||||||
|
acc |= encode4b6bList[inData.get(i) & 0x0f];
|
||||||
|
bitcount += 6;
|
||||||
|
|
||||||
|
while (bitcount >= 8) {
|
||||||
|
byte outByte = (byte) (acc >> (bitcount - 8) & 0xff);
|
||||||
|
outData.add(outByte);
|
||||||
|
bitcount -= 8;
|
||||||
|
acc &= (0xffff >> (16 - bitcount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bitcount > 0) {
|
||||||
|
acc <<= 6;
|
||||||
|
acc |= 0x14; // marks uneven packet boundary.
|
||||||
|
bitcount += 6;
|
||||||
|
if (bitcount >= 8) {
|
||||||
|
byte outByte = (byte) ((acc >> (bitcount - 8)) & 0xff);
|
||||||
|
outData.add(outByte);
|
||||||
|
bitcount -= 8;
|
||||||
|
// acc &= (0xffff >> (16 - bitcount));
|
||||||
|
}
|
||||||
|
while (bitcount >= 8) {
|
||||||
|
outData.add((byte) 0);
|
||||||
|
bitcount -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to byte[]
|
||||||
|
byte[] rval = ByteUtil.getByteArrayFromList(outData);
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode by Geoff
|
||||||
|
*
|
||||||
|
* @param raw
|
||||||
|
* @return
|
||||||
|
* @throws NumberFormatException
|
||||||
|
*/
|
||||||
|
public byte[] decode4b6b(byte[] raw) throws RileyLinkCommunicationException {
|
||||||
|
|
||||||
|
StringBuilder errorMessageBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
errorMessageBuilder.append("Input data: " + ByteUtil.shortHexString(raw) + "\n");
|
||||||
|
|
||||||
|
if ((raw.length % 2) != 0) {
|
||||||
|
errorMessageBuilder.append("Warn: odd number of bytes.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] rval = new byte[]{};
|
||||||
|
int availableBits = 0;
|
||||||
|
int codingErrors = 0;
|
||||||
|
int x = 0;
|
||||||
|
// Log.w(TAG,"decode4b6b: untested code");
|
||||||
|
// Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw)));
|
||||||
|
for (int i = 0; i < raw.length; i++) {
|
||||||
|
int unsignedValue = raw[i];
|
||||||
|
if (unsignedValue < 0) {
|
||||||
|
unsignedValue += 256;
|
||||||
|
}
|
||||||
|
x = (x << 8) + unsignedValue;
|
||||||
|
availableBits += 8;
|
||||||
|
if (availableBits >= 12) {
|
||||||
|
// take top six
|
||||||
|
int highcode = (x >> (availableBits - 6)) & 0x3F;
|
||||||
|
int highIndex = encode4b6bListIndex((byte) (highcode));
|
||||||
|
// take bottom six
|
||||||
|
int lowcode = (x >> (availableBits - 12)) & 0x3F;
|
||||||
|
int lowIndex = encode4b6bListIndex((byte) (lowcode));
|
||||||
|
// special case at end of transmission on uneven boundaries:
|
||||||
|
if ((highIndex >= 0) && (lowIndex >= 0)) {
|
||||||
|
byte decoded = (byte) ((highIndex << 4) + lowIndex);
|
||||||
|
rval = ByteUtil.concat(rval, decoded);
|
||||||
|
/*
|
||||||
|
* LOG.debug(String.format(
|
||||||
|
* "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s"
|
||||||
|
* ,
|
||||||
|
* i,x,highcode,highIndex, lowcode,
|
||||||
|
* lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString
|
||||||
|
* (ByteUtil.substring(raw,i+1,raw.length-i-1))));
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
// LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits));
|
||||||
|
errorMessageBuilder.append(String.format(
|
||||||
|
"decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n",
|
||||||
|
i, x, highcode, lowcode, availableBits));
|
||||||
|
codingErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
availableBits -= 12;
|
||||||
|
x = x & (0x0000ffff >> (16 - availableBits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableBits != 0) {
|
||||||
|
if ((availableBits == 4) && (x == 0x05)) {
|
||||||
|
// normal end
|
||||||
|
} else {
|
||||||
|
// LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits +
|
||||||
|
// ")");
|
||||||
|
errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)("
|
||||||
|
+ availableBits + ")\n");
|
||||||
|
codingErrors++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// also normal end.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codingErrors > 0) {
|
||||||
|
// LOG.error("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||||
|
errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||||
|
writeError(LOG, raw, errorMessageBuilder.toString());
|
||||||
|
throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString());
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static RFTools.DecodeResponseDto decode4b6bWithoutException(byte[] raw) {
|
||||||
|
// /*
|
||||||
|
// * if ((raw.length % 2) != 0) {
|
||||||
|
// * LOG.error("Warning: data is odd number of bytes");
|
||||||
|
// * }
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// RFTools.DecodeResponseDto response = new RFTools.DecodeResponseDto();
|
||||||
|
//
|
||||||
|
// StringBuilder errorMessageBuilder = new StringBuilder();
|
||||||
|
//
|
||||||
|
// errorMessageBuilder.append("Input data: " + ByteUtil.getHex(raw) + "\n");
|
||||||
|
//
|
||||||
|
// if ((raw.length % 2) != 0) {
|
||||||
|
// errorMessageBuilder.append("Warn: odd number of bytes.");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// byte[] rval = new byte[] {};
|
||||||
|
// int availableBits = 0;
|
||||||
|
// int codingErrors = 0;
|
||||||
|
// int x = 0;
|
||||||
|
// // Log.w(TAG,"decode4b6b: untested code");
|
||||||
|
// // Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw)));
|
||||||
|
// for (int i = 0; i < raw.length; i++) {
|
||||||
|
// int unsignedValue = raw[i];
|
||||||
|
// if (unsignedValue < 0) {
|
||||||
|
// unsignedValue += 256;
|
||||||
|
// }
|
||||||
|
// x = (x << 8) + unsignedValue;
|
||||||
|
// availableBits += 8;
|
||||||
|
// if (availableBits >= 12) {
|
||||||
|
// // take top six
|
||||||
|
// int highcode = (x >> (availableBits - 6)) & 0x3F;
|
||||||
|
// int highIndex = encode4b6bListIndex((byte)(highcode));
|
||||||
|
// // take bottom six
|
||||||
|
// int lowcode = (x >> (availableBits - 12)) & 0x3F;
|
||||||
|
// int lowIndex = encode4b6bListIndex((byte)(lowcode));
|
||||||
|
// // special case at end of transmission on uneven boundaries:
|
||||||
|
// if ((highIndex >= 0) && (lowIndex >= 0)) {
|
||||||
|
// byte decoded = (byte)((highIndex << 4) + lowIndex);
|
||||||
|
// rval = ByteUtil.concat(rval, decoded);
|
||||||
|
// /*
|
||||||
|
// * LOG.debug(String.format(
|
||||||
|
// *
|
||||||
|
// "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s"
|
||||||
|
// * ,
|
||||||
|
// * i,x,highcode,highIndex, lowcode,
|
||||||
|
// * lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString
|
||||||
|
// * (ByteUtil.substring(raw,i+1,raw.length-i-1))));
|
||||||
|
// */
|
||||||
|
// } else {
|
||||||
|
// //
|
||||||
|
// LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits));
|
||||||
|
// errorMessageBuilder.append(String.format(
|
||||||
|
// "decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n",
|
||||||
|
// i, x, highcode, lowcode, availableBits));
|
||||||
|
// codingErrors++;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// availableBits -= 12;
|
||||||
|
// x = x & (0x0000ffff >> (16 - availableBits));
|
||||||
|
// } else {
|
||||||
|
// // LOG.debug(String.format("i=%d, skip: x=0x%08X, available bits %d",i,x,availableBits));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (availableBits != 0) {
|
||||||
|
// if ((availableBits == 4) && (x == 0x05)) {
|
||||||
|
// // normal end
|
||||||
|
// } else {
|
||||||
|
// LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + ")");
|
||||||
|
// errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)("
|
||||||
|
// + availableBits + ")\n");
|
||||||
|
// codingErrors++;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // also normal end.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (codingErrors > 0) {
|
||||||
|
// LOG.error("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||||
|
// errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||||
|
//
|
||||||
|
// response.errorData = errorMessageBuilder.toString();
|
||||||
|
// } else {
|
||||||
|
// response.data = rval;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return response;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Encoding4b6bGo extends Encoding4b6bAbstract {
|
||||||
|
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGo.class);
|
||||||
|
private static Map<Short, Short> decodeGoMap;
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] encode4b6b(byte[] src) {
|
||||||
|
// 2 input bytes produce 3 output bytes.
|
||||||
|
// Odd final input byte, if any, produces 2 output bytes.
|
||||||
|
int n = src.length;
|
||||||
|
byte[] dst = new byte[3 * (n / 2) + 2 * (n % 2)];
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i += 2, j = j + 3) {
|
||||||
|
short x = convertUnsigned(src[i]);
|
||||||
|
short a = encode4b6bList[hi(4, x)];
|
||||||
|
short b = encode4b6bList[lo(4, x)];
|
||||||
|
dst[j] = (byte)(a << 2 | hi(4, b));
|
||||||
|
if (i + 1 < n) {
|
||||||
|
short y = convertUnsigned(src[i + 1]);
|
||||||
|
short c = encode4b6bList[hi(4, y)];
|
||||||
|
short d = encode4b6bList[lo(4, y)];
|
||||||
|
dst[j + 1] = (byte)(lo(4, b) << 4 | hi(6, c));
|
||||||
|
dst[j + 2] = (byte)(lo(2, c) << 6 | d);
|
||||||
|
} else {
|
||||||
|
// Fill final nibble with 5 to match pump behavior.
|
||||||
|
dst[j + 1] = (byte)(lo(4, b) << 4 | 0x5);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode from Go code by ecc1. NOT WORKING
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public byte[] decode4b6b(byte[] src) throws RileyLinkCommunicationException {
|
||||||
|
int n = src.length;
|
||||||
|
|
||||||
|
if (decodeGoMap == null)
|
||||||
|
initDecodeGo();
|
||||||
|
|
||||||
|
StringBuilder errorMessageBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
errorMessageBuilder.append("Input data: " + ByteUtil.getHex(src) + "\n");
|
||||||
|
int codingErrors = 0;
|
||||||
|
|
||||||
|
// Check for valid packet length.
|
||||||
|
if (n % 3 == 1) {
|
||||||
|
errorMessageBuilder.append("Invalid package length " + n);
|
||||||
|
codingErrors++;
|
||||||
|
// return nil, ErrDecoding
|
||||||
|
}
|
||||||
|
// 3 input bytes produce 2 output bytes.
|
||||||
|
// Final 2 input bytes, if any, produce 1 output byte.
|
||||||
|
byte[] dst = new byte[2 * (n / 3) + (n % 3) / 2];
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < n; i = i + 3, j = j + 2) {
|
||||||
|
if (i + 1 >= n) {
|
||||||
|
errorMessageBuilder.append("Overflow in i (" + i + ")");
|
||||||
|
}
|
||||||
|
short x = convertUnsigned(src[i]);
|
||||||
|
short y = convertUnsigned(src[i + 1]);
|
||||||
|
short a = decode6b_goMap(hi(6, x));
|
||||||
|
short b = decode6b_goMap(lo(2, x) << 4 | hi(4, y));
|
||||||
|
if (a == 0xFF || b == 0xFF) {
|
||||||
|
errorMessageBuilder.append("Error decoding ");
|
||||||
|
codingErrors++;
|
||||||
|
}
|
||||||
|
dst[j] = (byte)(a << 4 | b);
|
||||||
|
if (i + 2 < n) {
|
||||||
|
short z = convertUnsigned(src[i + 2]);
|
||||||
|
short c = decode6b_goMap(lo(4, y) << 2 | hi(2, z));
|
||||||
|
short d = decode6b_goMap(lo(6, z));
|
||||||
|
if (c == 0xFF || d == 0xFF) {
|
||||||
|
errorMessageBuilder.append("Error decoding ");
|
||||||
|
codingErrors++;
|
||||||
|
}
|
||||||
|
dst[j + 1] = (byte)(c << 4 | d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codingErrors > 0) {
|
||||||
|
errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||||
|
writeError(LOG, dst, errorMessageBuilder.toString());
|
||||||
|
throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static short hi(int n, short x) {
|
||||||
|
// x = convertUnsigned(x);
|
||||||
|
return (short)(x >> (8 - n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static short lo(int n, short x) {
|
||||||
|
// byte b = (byte)x;
|
||||||
|
return (short)(x & ((1 << n) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void initDecodeGo() {
|
||||||
|
|
||||||
|
decodeGoMap = new HashMap<>();
|
||||||
|
|
||||||
|
putToMap(0x0B, 0x0B);
|
||||||
|
putToMap(0x0D, 0x0D);
|
||||||
|
putToMap(0x0E, 0x0E);
|
||||||
|
putToMap(0x15, 0x00);
|
||||||
|
putToMap(0x16, 0x07);
|
||||||
|
putToMap(0x19, 0x09);
|
||||||
|
putToMap(0x1A, 0x08);
|
||||||
|
putToMap(0x1C, 0x0F);
|
||||||
|
putToMap(0x23, 0x03);
|
||||||
|
putToMap(0x25, 0x05);
|
||||||
|
putToMap(0x26, 0x06);
|
||||||
|
putToMap(0x2A, 0x0A);
|
||||||
|
putToMap(0x2C, 0x0C);
|
||||||
|
putToMap(0x31, 0x01);
|
||||||
|
putToMap(0x32, 0x02);
|
||||||
|
putToMap(0x34, 0x04);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static short decode6b_goMap(int value) {
|
||||||
|
short val = (short)value;
|
||||||
|
if (decodeGoMap.containsKey(val))
|
||||||
|
return decodeGoMap.get(val);
|
||||||
|
else
|
||||||
|
return (short)0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void putToMap(int val1, int val2) {
|
||||||
|
decodeGoMap.put((short)val1, (short)val2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Encoding4b6bLoop extends Encoding4b6bAbstract {
|
||||||
|
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bLoop.class);
|
||||||
|
public Map<Integer, Byte> codesRev = null;
|
||||||
|
|
||||||
|
|
||||||
|
public Encoding4b6bLoop() {
|
||||||
|
createCodeRev();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is almost 1:1 with same method from Loop, only change is unsigning of element and |05 added for
|
||||||
|
* last byte. It should work better than original one, which is really different than this one.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public byte[] encode4b6b(byte[] data) {
|
||||||
|
|
||||||
|
List<Byte> buffer = new ArrayList<Byte>();
|
||||||
|
int bitAccumulator = 0x0;
|
||||||
|
int bitcount = 0;
|
||||||
|
|
||||||
|
for (byte element : data) {
|
||||||
|
|
||||||
|
short element2 = element;
|
||||||
|
|
||||||
|
if (element2 < 0) {
|
||||||
|
element2 += 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitAccumulator <<= 6;
|
||||||
|
bitAccumulator |= encode4b6bList[element2 >> 4];
|
||||||
|
bitcount += 6;
|
||||||
|
|
||||||
|
bitAccumulator <<= 6;
|
||||||
|
bitAccumulator |= encode4b6bList[element2 & 0x0f];
|
||||||
|
bitcount += 6;
|
||||||
|
|
||||||
|
while (bitcount >= 8) {
|
||||||
|
buffer.add((byte)((bitAccumulator >> (bitcount - 8)) & 0xff));
|
||||||
|
bitcount -= 8;
|
||||||
|
bitAccumulator &= (0xffff >> (16 - bitcount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitcount > 0) {
|
||||||
|
bitAccumulator <<= (8 - bitcount);
|
||||||
|
buffer.add((byte)((bitAccumulator | 0x5) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteUtil.getByteArrayFromList(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOESN'T WORK YET
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
* @throws RileyLinkCommunicationException
|
||||||
|
*/
|
||||||
|
public byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException {
|
||||||
|
List<Byte> buffer = new ArrayList<Byte>();
|
||||||
|
int availBits = 0;
|
||||||
|
int bitAccumulator = 0;
|
||||||
|
|
||||||
|
for (byte element2 : data) {
|
||||||
|
|
||||||
|
short element = convertUnsigned(element2);
|
||||||
|
|
||||||
|
// if (element < 0) {
|
||||||
|
// element += 255;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (element == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitAccumulator = (bitAccumulator << 8) + element;
|
||||||
|
availBits += 8;
|
||||||
|
|
||||||
|
if (availBits >= 12) {
|
||||||
|
|
||||||
|
int hiNibble;
|
||||||
|
int loNibble;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int index = (bitAccumulator >> (availBits - 6));
|
||||||
|
int index2 = ((bitAccumulator >> (availBits - 12)) & 0b111111);
|
||||||
|
hiNibble = codesRev.get((bitAccumulator >> (availBits - 6)));
|
||||||
|
loNibble = codesRev.get(((bitAccumulator >> (availBits - 12)) & 0b111111));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.out.println("Exception: " + ex.getMessage());
|
||||||
|
ex.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int decoded = ((hiNibble << 4) + loNibble);
|
||||||
|
buffer.add((byte)decoded);
|
||||||
|
availBits -= 12;
|
||||||
|
bitAccumulator = bitAccumulator & (0xffff >> (16 - availBits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteUtil.getByteArrayFromList(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createCodeRev() {
|
||||||
|
codesRev = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < encode4b6bList.length; i++) {
|
||||||
|
codesRev.put(i, encode4b6bList[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 21/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum CC111XRegister {
|
||||||
|
|
||||||
|
sync1(0x00), //
|
||||||
|
sync0(0x01), //
|
||||||
|
pktlen(0x02), //
|
||||||
|
pktctrl1(0x03), //
|
||||||
|
pktctrl0(0x04), //
|
||||||
|
fsctrl1(0x07), //
|
||||||
|
freq2(0x09), //
|
||||||
|
freq1(0x0a), //
|
||||||
|
freq0(0x0b), //
|
||||||
|
mdmcfg4(0x0c), //
|
||||||
|
mdmcfg3(0x0d), //
|
||||||
|
mdmcfg2(0x0e), //
|
||||||
|
mdmcfg1(0x0f), //
|
||||||
|
mdmcfg0(0x10), //
|
||||||
|
deviatn(0x11), //
|
||||||
|
mcsm0(0x14), //
|
||||||
|
foccfg(0x15), //
|
||||||
|
agcctrl2(0x17), //
|
||||||
|
agcctrl1(0x18), //
|
||||||
|
agcctrl0(0x19), //
|
||||||
|
frend1(0x1a), //
|
||||||
|
frend0(0x1b), //
|
||||||
|
fscal3(0x1c), //
|
||||||
|
fscal2(0x1d), //
|
||||||
|
fscal1(0x1e), //
|
||||||
|
fscal0(0x1f), //
|
||||||
|
test1(0x24), //
|
||||||
|
test0(0x25), //
|
||||||
|
paTable0(0x2e), //
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public byte value;
|
||||||
|
|
||||||
|
|
||||||
|
CC111XRegister(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 22/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RFSpyCommand {
|
||||||
|
|
||||||
|
GetState(1), //
|
||||||
|
GetVersion(2, false), //
|
||||||
|
GetPacket(3), // aka Listen, receive
|
||||||
|
Send(4), //
|
||||||
|
SendAndListen(5), //
|
||||||
|
UpdateRegister(6), //
|
||||||
|
Reset(7), //
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public byte code;
|
||||||
|
private boolean encoded = true;
|
||||||
|
|
||||||
|
|
||||||
|
RFSpyCommand(int code) {
|
||||||
|
this.code = (byte)code;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RFSpyCommand(int code, boolean encoded) {
|
||||||
|
this.code = (byte)code;
|
||||||
|
this.encoded = encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isEncoded() {
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setEncoded(boolean encoded) {
|
||||||
|
this.encoded = encoded;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
public enum RFSpyRLResponse {
|
||||||
|
// 0xaa == timeout
|
||||||
|
// 0xbb == interrupted
|
||||||
|
// 0xcc == zero-data
|
||||||
|
// 0xdd == success
|
||||||
|
// 0x11 == invalidParam
|
||||||
|
// 0x22 == unknownCommand
|
||||||
|
|
||||||
|
Invalid(0), // default, just fail
|
||||||
|
Timeout(0xAA),
|
||||||
|
Interrupted(0xBB),
|
||||||
|
ZeroData(0xCC),
|
||||||
|
Success(0xDD),
|
||||||
|
OldSuccess(0x01),
|
||||||
|
InvalidParam(0x11),
|
||||||
|
UnknownCommand(0x22), ;
|
||||||
|
|
||||||
|
byte value;
|
||||||
|
|
||||||
|
|
||||||
|
RFSpyRLResponse(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RFSpyRLResponse fromByte(byte input) {
|
||||||
|
for (RFSpyRLResponse type : values()) {
|
||||||
|
if (type.value == input) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/6/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RLMessageType {
|
||||||
|
PowerOn, // for powering on the pump (wakeup)
|
||||||
|
ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel)
|
||||||
|
;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 21/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RXFilterMode {
|
||||||
|
|
||||||
|
Wide(0x50), //
|
||||||
|
Narrow(0x90) //
|
||||||
|
;
|
||||||
|
|
||||||
|
public byte value;
|
||||||
|
|
||||||
|
|
||||||
|
RXFilterMode(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/24/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkBLEError {
|
||||||
|
CodingErrors("Coding Errors encountered during decode of RileyLink packet."), //
|
||||||
|
Timeout("Timeout"), //
|
||||||
|
Interrupted("Interrupted"),
|
||||||
|
TooShortOrNullResponse("Too short or null decoded response.");
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkBLEError(String description) {
|
||||||
|
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 22/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkCommandType {
|
||||||
|
|
||||||
|
GetState(1), //
|
||||||
|
GetVersion(2), //
|
||||||
|
GetPacket(3), // aka Listen, receive
|
||||||
|
Send(4), //
|
||||||
|
SendAndListen(5), //
|
||||||
|
UpdateRegister(6), //
|
||||||
|
Reset(7), //
|
||||||
|
Led(8),
|
||||||
|
ReadRegister(9),
|
||||||
|
SetModeRegisters(10),
|
||||||
|
SetHardwareEncoding(11),
|
||||||
|
SetPreamble(12),
|
||||||
|
ResetRadioConfig(13),
|
||||||
|
GetStatistics(14), ;
|
||||||
|
|
||||||
|
public byte code;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkCommandType(int code) {
|
||||||
|
this.code = (byte)code;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
|
||||||
|
public enum RileyLinkEncodingType {
|
||||||
|
|
||||||
|
None(0x00, null), // No encoding on RL
|
||||||
|
Manchester(0x01, null), // Manchester encoding on RL (for Omnipod)
|
||||||
|
FourByteSixByteRileyLink(0x02, R.string.medtronic_pump_encoding_4b6b_rileylink), // 4b6b encoding on RL (for Medtronic)
|
||||||
|
FourByteSixByteLocal(0x00, R.string.medtronic_pump_encoding_4b6b_local), // No encoding on RL, but 4b6b encoding in code
|
||||||
|
;
|
||||||
|
|
||||||
|
public byte value;
|
||||||
|
public Integer resourceId;
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
private static Map<String, RileyLinkEncodingType> encodingTypeMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
encodingTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (RileyLinkEncodingType encType : values()) {
|
||||||
|
if (encType.resourceId!=null) {
|
||||||
|
encodingTypeMap.put(MainApp.gs(encType.resourceId), encType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkEncodingType(int value) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkEncodingType(int value, Integer resourceId) {
|
||||||
|
this.value = (byte)value;
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RileyLinkEncodingType getByDescription(String description) {
|
||||||
|
if (encodingTypeMap.containsKey(description)) {
|
||||||
|
return encodingTypeMap.get(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RileyLinkEncodingType.FourByteSixByteLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public enum RileyLinkFirmwareVersion {
|
||||||
|
|
||||||
|
Version_0_0(0, 0, "0.0"), // just for defaulting
|
||||||
|
Version_0_9(0, 9, "0.9"), //
|
||||||
|
Version_1_0(1, 0, "1.0"), //
|
||||||
|
Version_2_0(2, 0, "2.0"), //
|
||||||
|
Version_2_2(2, 2, "2.2"), //
|
||||||
|
Version_3_0(3, 0, "3.0"), //
|
||||||
|
UnknownVersion(0, 0, "???"), //
|
||||||
|
Version1(Version_0_0, Version_0_9, Version_1_0), //
|
||||||
|
Version2(Version_2_0, Version_2_2), //
|
||||||
|
Version2AndHigher(Version_2_0, Version_2_2, Version_3_0), //
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final String FIRMWARE_IDENTIFICATION_PREFIX = "subg_rfspy ";
|
||||||
|
private static final Pattern _version_pattern = Pattern.compile(FIRMWARE_IDENTIFICATION_PREFIX
|
||||||
|
+ "([0-9]+)\\.([0-9]+)");
|
||||||
|
static Map<String, RileyLinkFirmwareVersion> mapByVersion;
|
||||||
|
|
||||||
|
static {
|
||||||
|
mapByVersion = new HashMap<>();
|
||||||
|
for (RileyLinkFirmwareVersion version : values()) {
|
||||||
|
if (version.familyMembers == null) {
|
||||||
|
mapByVersion.put(version.versionKey, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RileyLinkFirmwareVersion[] familyMembers;
|
||||||
|
private int major;
|
||||||
|
private int minor;
|
||||||
|
private String versionKey = "";
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkFirmwareVersion(int major, int minor, String versionKey) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.versionKey = versionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkFirmwareVersion(RileyLinkFirmwareVersion... familyMembers) {
|
||||||
|
this.familyMembers = familyMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSameVersion(RileyLinkFirmwareVersion versionWeCheck, RileyLinkFirmwareVersion versionSources) {
|
||||||
|
if (versionSources.familyMembers != null) {
|
||||||
|
for (RileyLinkFirmwareVersion vrs : versionSources.familyMembers) {
|
||||||
|
if (vrs == versionWeCheck)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (versionWeCheck == versionSources);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RileyLinkFirmwareVersion getByVersionString(String versionString) {
|
||||||
|
if (versionString != null) {
|
||||||
|
Matcher m = _version_pattern.matcher(versionString);
|
||||||
|
if (m.find()) {
|
||||||
|
int major = Integer.parseInt(m.group(1));
|
||||||
|
int minor = Integer.parseInt(m.group(2));
|
||||||
|
String versionKey = major + "." + minor;
|
||||||
|
if (mapByVersion.containsKey(versionKey)) {
|
||||||
|
return mapByVersion.get(versionKey);
|
||||||
|
} else {
|
||||||
|
return defaultToLowestMajorVersion(major); // just in case there is new release that we don't cover
|
||||||
|
// example: 2.3 etc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RileyLinkFirmwareVersion.UnknownVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static RileyLinkFirmwareVersion defaultToLowestMajorVersion(int major) {
|
||||||
|
if (mapByVersion.containsKey(major + ".0")) {
|
||||||
|
return mapByVersion.get(major + ".0");
|
||||||
|
}
|
||||||
|
return UnknownVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isSameVersion(RileyLinkFirmwareVersion versionSources) {
|
||||||
|
return isSameVersion(this, versionSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return FIRMWARE_IDENTIFICATION_PREFIX + versionKey;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/7/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkTargetFrequency {
|
||||||
|
|
||||||
|
Medtronic_WorldWide(868.25, 868.3, 868.35, 868.4, 868.45, 868.5, 868.55, 868.6, 868.65), //
|
||||||
|
Medtronic_US(916.45, 916.5, 916.55, 916.6, 916.65, 916.7, 916.75, 916.8), //
|
||||||
|
Omnipod(433.91), //
|
||||||
|
;
|
||||||
|
|
||||||
|
double[] frequencies;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkTargetFrequency(double... frequencies) {
|
||||||
|
this.frequencies = frequencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double[] getScanFrequencies() {
|
||||||
|
return frequencies;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public abstract class BLECommOperation {
|
||||||
|
|
||||||
|
public boolean timedOut = false;
|
||||||
|
public boolean interrupted = false;
|
||||||
|
protected byte[] value;
|
||||||
|
protected BluetoothGatt gatt;
|
||||||
|
protected Semaphore operationComplete = new Semaphore(0, true);
|
||||||
|
|
||||||
|
|
||||||
|
// This is to be run on the main thread
|
||||||
|
public abstract void execute(RileyLinkBLE comm);
|
||||||
|
|
||||||
|
|
||||||
|
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getGattOperationTimeout_ms() {
|
||||||
|
return 22000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public byte[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class BLECommOperationResult {
|
||||||
|
|
||||||
|
public static final int RESULT_NONE = 0;
|
||||||
|
public static final int RESULT_SUCCESS = 1;
|
||||||
|
public static final int RESULT_TIMEOUT = 2;
|
||||||
|
public static final int RESULT_BUSY = 3;
|
||||||
|
public static final int RESULT_INTERRUPTED = 4;
|
||||||
|
public static final int RESULT_NOT_CONFIGURED = 5;
|
||||||
|
public byte[] value;
|
||||||
|
public int resultCode;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class CharacteristicReadOperation extends BLECommOperation {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
|
||||||
|
private BluetoothGattCharacteristic characteristic;
|
||||||
|
|
||||||
|
|
||||||
|
public CharacteristicReadOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara) {
|
||||||
|
this.gatt = gatt;
|
||||||
|
this.characteristic = chara;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(RileyLinkBLE comm) {
|
||||||
|
gatt.readCharacteristic(characteristic);
|
||||||
|
// wait here for callback to notify us that value was read.
|
||||||
|
try {
|
||||||
|
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||||
|
if (didAcquire) {
|
||||||
|
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||||
|
// understanding of the sequence of events.
|
||||||
|
// success
|
||||||
|
} else {
|
||||||
|
LOG.error("Timeout waiting for gatt write operation to complete");
|
||||||
|
timedOut = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.error("Interrupted while waiting for gatt write operation to complete");
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
value = characteristic.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||||
|
super.gattOperationCompletionCallback(uuid, value);
|
||||||
|
if (!characteristic.getUuid().equals(uuid)) {
|
||||||
|
LOG.error(String.format(
|
||||||
|
"Completion callback: UUID does not match! out of sequence? Found: %s, should be %s",
|
||||||
|
GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid)));
|
||||||
|
}
|
||||||
|
operationComplete.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPBTCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class CharacteristicWriteOperation extends BLECommOperation {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||||
|
|
||||||
|
private BluetoothGattCharacteristic characteristic;
|
||||||
|
|
||||||
|
|
||||||
|
public CharacteristicWriteOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara, byte[] value) {
|
||||||
|
this.gatt = gatt;
|
||||||
|
this.characteristic = chara;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(RileyLinkBLE comm) {
|
||||||
|
|
||||||
|
characteristic.setValue(value);
|
||||||
|
gatt.writeCharacteristic(characteristic);
|
||||||
|
// wait here for callback to notify us that value was written.
|
||||||
|
try {
|
||||||
|
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||||
|
if (didAcquire) {
|
||||||
|
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||||
|
// understanding of the sequence of events.
|
||||||
|
// success
|
||||||
|
} else {
|
||||||
|
LOG.error("Timeout waiting for gatt write operation to complete");
|
||||||
|
timedOut = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("Interrupted while waiting for gatt write operation to complete");
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This will be run on the IBinder thread
|
||||||
|
@Override
|
||||||
|
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||||
|
if (!characteristic.getUuid().equals(uuid)) {
|
||||||
|
LOG.error(String.format(
|
||||||
|
"Completion callback: UUID does not match! out of sequence? Found: %s, should be %s",
|
||||||
|
GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid)));
|
||||||
|
}
|
||||||
|
operationComplete.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPBTCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/26/16.
|
||||||
|
*/
|
||||||
|
public class DescriptorWriteOperation extends BLECommOperation {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DescriptorWriteOperation.class);
|
||||||
|
|
||||||
|
private BluetoothGattDescriptor descr;
|
||||||
|
|
||||||
|
|
||||||
|
public DescriptorWriteOperation(BluetoothGatt gatt, BluetoothGattDescriptor descr, byte[] value) {
|
||||||
|
this.gatt = gatt;
|
||||||
|
this.descr = descr;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||||
|
super.gattOperationCompletionCallback(uuid, value);
|
||||||
|
operationComplete.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(RileyLinkBLE comm) {
|
||||||
|
descr.setValue(value);
|
||||||
|
gatt.writeDescriptor(descr);
|
||||||
|
// wait here for callback to notify us that value was read.
|
||||||
|
try {
|
||||||
|
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||||
|
if (didAcquire) {
|
||||||
|
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||||
|
// understanding of the sequence of events.
|
||||||
|
// success
|
||||||
|
} else {
|
||||||
|
LOG.error("Timeout waiting for descriptor write operation to complete");
|
||||||
|
timedOut = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("Interrupted while waiting for descriptor write operation to complete");
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 9/10/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BleAdvertisedData {
|
||||||
|
|
||||||
|
private List<UUID> mUuids;
|
||||||
|
private String mName;
|
||||||
|
|
||||||
|
|
||||||
|
public BleAdvertisedData(List<UUID> uuids, String name) {
|
||||||
|
mUuids = uuids;
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<UUID> getUuids() {
|
||||||
|
return mUuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "BleAdvertisedData [name=" + mName + ", UUIDs=" + mUuids + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 4/5/19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class CommandValueDefinition {
|
||||||
|
|
||||||
|
public CommandValueDefinitionType definitionType;
|
||||||
|
public String value;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RLHistoryItem {
|
||||||
|
|
||||||
|
private MedtronicCommandType medtronicCommandType;
|
||||||
|
private LocalDateTime dateTime;
|
||||||
|
private RLHistoryItemSource source;
|
||||||
|
private RileyLinkServiceState serviceState;
|
||||||
|
private RileyLinkError errorCode;
|
||||||
|
|
||||||
|
private RileyLinkTargetDevice targetDevice;
|
||||||
|
private PumpDeviceState pumpDeviceState;
|
||||||
|
|
||||||
|
|
||||||
|
public RLHistoryItem(RileyLinkServiceState serviceState, RileyLinkError errorCode,
|
||||||
|
RileyLinkTargetDevice targetDevice) {
|
||||||
|
this.targetDevice = targetDevice;
|
||||||
|
this.dateTime = new LocalDateTime();
|
||||||
|
this.serviceState = serviceState;
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.source = RLHistoryItemSource.RileyLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RLHistoryItem(PumpDeviceState pumpDeviceState, RileyLinkTargetDevice targetDevice) {
|
||||||
|
this.pumpDeviceState = pumpDeviceState;
|
||||||
|
this.dateTime = new LocalDateTime();
|
||||||
|
this.targetDevice = targetDevice;
|
||||||
|
this.source = RLHistoryItemSource.MedtronicPump;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RLHistoryItem(MedtronicCommandType medtronicCommandType) {
|
||||||
|
this.dateTime = new LocalDateTime();
|
||||||
|
this.medtronicCommandType = medtronicCommandType;
|
||||||
|
source = RLHistoryItemSource.MedtronicCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LocalDateTime getDateTime() {
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkServiceState getServiceState() {
|
||||||
|
return serviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkError getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
|
||||||
|
// TODO extend when we have Omnipod
|
||||||
|
switch (this.source) {
|
||||||
|
case RileyLink:
|
||||||
|
return "State: " + MainApp.gs(serviceState.getResourceId(targetDevice))
|
||||||
|
+ (this.errorCode == null ? "" : ", Error Code: " + errorCode);
|
||||||
|
|
||||||
|
case MedtronicPump:
|
||||||
|
return MainApp.gs(pumpDeviceState.getResourceId());
|
||||||
|
|
||||||
|
case MedtronicCommand:
|
||||||
|
return medtronicCommandType.name();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Unknown Description";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RLHistoryItemSource getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpDeviceState getPumpDeviceState() {
|
||||||
|
return pumpDeviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RLHistoryItemSource {
|
||||||
|
RileyLink("RileyLink"), //
|
||||||
|
MedtronicPump("Medtronic"), //
|
||||||
|
MedtronicCommand("Medtronic");
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
|
||||||
|
RLHistoryItemSource(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 4/5/19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum CommandValueDefinitionRLType implements CommandValueDefinitionType {
|
||||||
|
Name, //
|
||||||
|
Firmware, //
|
||||||
|
SignalStrength, //
|
||||||
|
ConnectionState, //
|
||||||
|
Frequency, //
|
||||||
|
;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String commandAction() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 4/5/19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface CommandValueDefinitionType {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
|
||||||
|
String commandAction();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 14/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkError {
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
|
||||||
|
// BT
|
||||||
|
NoBluetoothAdapter(R.string.rileylink_error_no_bt_adapter), //
|
||||||
|
BluetoothDisabled(R.string.rileylink_error_bt_disabled), //
|
||||||
|
|
||||||
|
// RileyLink
|
||||||
|
RileyLinkUnreachable(R.string.rileylink_error_unreachable), //
|
||||||
|
DeviceIsNotRileyLink(R.string.rileylink_error_not_rl), //
|
||||||
|
|
||||||
|
// Device
|
||||||
|
TuneUpOfDeviceFailed(R.string.rileylink_error_tuneup_failed), //
|
||||||
|
NoContactWithDevice(R.string.rileylink_error_pump_unreachable, R.string.rileylink_error_pod_unreachable), //
|
||||||
|
;
|
||||||
|
|
||||||
|
int resourceId;
|
||||||
|
Integer resourceIdPod;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkError(int resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkError(int resourceId, int resourceIdPod) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
this.resourceIdPod = resourceIdPod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getResourceId(RileyLinkTargetDevice targetDevice) {
|
||||||
|
if (this.resourceIdPod != null) {
|
||||||
|
|
||||||
|
return targetDevice == RileyLinkTargetDevice.MedtronicPump ? //
|
||||||
|
this.resourceId
|
||||||
|
: this.resourceIdPod;
|
||||||
|
} else {
|
||||||
|
return this.resourceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 14/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkServiceState {
|
||||||
|
|
||||||
|
NotStarted(R.string.rileylink_state_not_started), //
|
||||||
|
|
||||||
|
// Bluetooth
|
||||||
|
BluetoothInitializing(R.string.rileylink_state_bt_init), // (S) init BT (if error no BT interface -> Disabled, BT
|
||||||
|
// not enabled -> BluetoothError)
|
||||||
|
// BluetoothNotAvailable, // (E) BT not available, would happen only if device has no BT
|
||||||
|
BluetoothError(R.string.rileylink_state_bt_error), // (E) if BT gets disabled ( -> EnableBluetooth)
|
||||||
|
BluetoothReady(R.string.rileylink_state_bt_ready), // (OK)
|
||||||
|
|
||||||
|
// RileyLink
|
||||||
|
RileyLinkInitializing(R.string.rileylink_state_rl_init), // (S) start Gatt discovery (OK -> RileyLinkReady, Error ->
|
||||||
|
// BluetoothEnabled) ??
|
||||||
|
RileyLinkError(R.string.rileylink_state_rl_error), // (E)
|
||||||
|
RileyLinkReady(R.string.rileylink_state_connected), // (OK) if tunning was already done we go to PumpConnectorReady
|
||||||
|
|
||||||
|
// Tunning
|
||||||
|
TuneUpDevice(R.string.rileylink_state_pc_tune_up), // (S)
|
||||||
|
PumpConnectorError(R.string.rileylink_state_pc_error), // either TuneUp Error or pump couldn't not be contacted
|
||||||
|
// error
|
||||||
|
PumpConnectorReady(R.string.rileylink_state_connected), // (OK) RileyLink Ready for Pump Communication
|
||||||
|
|
||||||
|
// Initializing, // get all parameters required for connection (if not possible -> Disabled, if sucessful ->
|
||||||
|
// EnableBluetooth)
|
||||||
|
|
||||||
|
// EnableBlueTooth, // enable BT (if error no BT interface -> Disabled, BT not enabled -> BluetoothError)
|
||||||
|
// BlueToothEnabled, // -> InitializeRileyLink
|
||||||
|
// RileyLinkInitialized, //
|
||||||
|
|
||||||
|
// RileyLinkConnected, // -> TuneUpPump (on 1st), else PumpConnectorReady
|
||||||
|
|
||||||
|
// PumpConnected, //
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
int resourceId;
|
||||||
|
Integer resourceIdPod;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkServiceState(int resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkServiceState(int resourceId, int resourceIdPod) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
this.resourceIdPod = resourceIdPod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isReady(RileyLinkServiceState serviceState) {
|
||||||
|
return (/* serviceState == RileyLinkReady || */serviceState == PumpConnectorReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getResourceId(RileyLinkTargetDevice targetDevice) {
|
||||||
|
if (this.resourceIdPod != null) {
|
||||||
|
|
||||||
|
return (targetDevice == null || targetDevice == RileyLinkTargetDevice.MedtronicPump) ? //
|
||||||
|
this.resourceId
|
||||||
|
: this.resourceIdPod;
|
||||||
|
} else {
|
||||||
|
return this.resourceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isConnecting() {
|
||||||
|
|
||||||
|
return (this == RileyLinkServiceState.BluetoothInitializing || //
|
||||||
|
// this == RileyLinkServiceState.BluetoothError || //
|
||||||
|
this == RileyLinkServiceState.BluetoothReady || //
|
||||||
|
this == RileyLinkServiceState.RileyLinkInitializing || //
|
||||||
|
this == RileyLinkReady
|
||||||
|
// this == RileyLinkServiceState.RileyLinkBLEError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
|
||||||
|
return (this == RileyLinkServiceState.BluetoothError || //
|
||||||
|
// this == RileyLinkServiceState.PumpConnectorError || //
|
||||||
|
this == RileyLinkServiceState.RileyLinkError);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum RileyLinkTargetDevice {
|
||||||
|
MedtronicPump(R.string.rileylink_target_device_medtronic, true), //
|
||||||
|
Omnipod(R.string.rileylink_target_device_omnipod, false), //
|
||||||
|
;
|
||||||
|
|
||||||
|
private int resourceId;
|
||||||
|
private boolean tuneUpEnabled;
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkTargetDevice(int resourceId, boolean tuneUpEnabled) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
this.tuneUpEnabled = tuneUpEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isTuneUpEnabled() {
|
||||||
|
return tuneUpEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||||
|
|
||||||
|
public class RileyLinkStatusActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@BindView(R.id.rls_t1_connection_status)
|
||||||
|
TextView connectionStatus;
|
||||||
|
|
||||||
|
@BindView(R.id.rls_t1_configured_address)
|
||||||
|
TextView configuredAddress;
|
||||||
|
|
||||||
|
@BindView(R.id.rls_t1_connected_device)
|
||||||
|
TextView connectedDevice;
|
||||||
|
|
||||||
|
@BindView(R.id.rls_t1_connection_error)
|
||||||
|
TextView connectionError;
|
||||||
|
RileyLinkServiceData rileyLinkServiceData;
|
||||||
|
|
||||||
|
private SectionsPagerAdapter mSectionsPagerAdapter;
|
||||||
|
private FloatingActionButton floatingActionButton;
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
/**
|
||||||
|
* The {@link ViewPager} that will host the section contents.
|
||||||
|
*/
|
||||||
|
private ViewPager mViewPager;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.rileylink_status);
|
||||||
|
|
||||||
|
// Create the adapter that will return a fragment for each of the three
|
||||||
|
// primary sections of the activity.
|
||||||
|
|
||||||
|
// Set up the ViewPager with the sections adapter.
|
||||||
|
mViewPager = (ViewPager)findViewById(R.id.rileylink_settings_container);
|
||||||
|
// mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||||
|
setupViewPager(mViewPager);
|
||||||
|
|
||||||
|
tabLayout = (TabLayout)findViewById(R.id.rileylink_settings_tabs);
|
||||||
|
tabLayout.setupWithViewPager(mViewPager);
|
||||||
|
|
||||||
|
floatingActionButton = (FloatingActionButton)findViewById(R.id.rileylink_settings_fab);
|
||||||
|
floatingActionButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
RefreshableInterface selectableInterface = (RefreshableInterface)mSectionsPagerAdapter
|
||||||
|
.getItem(tabLayout.getSelectedTabPosition());
|
||||||
|
selectableInterface.refreshData();
|
||||||
|
|
||||||
|
// refreshData(tabLayout.getSelectedTabPosition());
|
||||||
|
|
||||||
|
// Toast.makeText(getApplicationContext(), "Test pos: " + tabLayout.getSelectedTabPosition(),
|
||||||
|
// Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connectionStatus = (TextView)findViewById(R.id.rls_t1_connection_status);
|
||||||
|
this.configuredAddress = (TextView)findViewById(R.id.rls_t1_configured_address);
|
||||||
|
this.connectedDevice = (TextView)findViewById(R.id.rls_t1_connected_device);
|
||||||
|
this.connectionError = (TextView)findViewById(R.id.rls_t1_connection_error);
|
||||||
|
|
||||||
|
rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||||
|
|
||||||
|
// // 7-12
|
||||||
|
// int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, //
|
||||||
|
// R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12};
|
||||||
|
//
|
||||||
|
// for (int id : ids) {
|
||||||
|
//
|
||||||
|
// TextView tv = (TextView) findViewById(id);
|
||||||
|
// tv.setText(tv.getText() + ":");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// refreshData(0);
|
||||||
|
// refreshData(1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void refreshData(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
// FIXME i18n
|
||||||
|
this.connectionStatus.setText(rileyLinkServiceData.serviceState.name());
|
||||||
|
this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress);
|
||||||
|
// FIXME
|
||||||
|
this.connectedDevice.setText("???");
|
||||||
|
// FIXME i18n
|
||||||
|
this.connectionError.setText(rileyLinkServiceData.errorCode.name());
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setupViewPager(ViewPager pager) {
|
||||||
|
|
||||||
|
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||||
|
|
||||||
|
mSectionsPagerAdapter.addFragment(new RileyLinkStatusGeneral(), MainApp.gs(R.string.rileylink_settings_tab1));
|
||||||
|
mSectionsPagerAdapter.addFragment(new RileyLinkStatusHistory(), MainApp.gs(R.string.rileylink_settings_tab2));
|
||||||
|
//mSectionsPagerAdapter.addFragment(new RileyLinkStatusDevice(), "Medtronic");
|
||||||
|
|
||||||
|
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
||||||
|
* one of the sections/tabs/pages.
|
||||||
|
*/
|
||||||
|
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
|
||||||
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
|
List<String> fragmentTitle = new ArrayList<>();
|
||||||
|
int lastSelectedPosition = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public SectionsPagerAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
this.lastSelectedPosition = position;
|
||||||
|
return fragmentList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
// Show 3 total pages.
|
||||||
|
return fragmentList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addFragment(Fragment fragment, String title) {
|
||||||
|
this.fragmentList.add(fragment);
|
||||||
|
this.fragmentTitle.add(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return fragmentTitle.get(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||||
|
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition;
|
||||||
|
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||||
|
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||||
|
//import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// FIXME needs to be implemented
|
||||||
|
|
||||||
|
public class RileyLinkStatusDevice extends Fragment implements RefreshableInterface {
|
||||||
|
|
||||||
|
ListView listView;
|
||||||
|
|
||||||
|
RileyLinkCommandListAdapter adapter;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.rileylink_status_device, container, false);
|
||||||
|
|
||||||
|
adapter = new RileyLinkCommandListAdapter();
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
this.listView = (ListView)getActivity().findViewById(R.id.rileyLinkDeviceList);
|
||||||
|
|
||||||
|
listView.setAdapter(adapter);
|
||||||
|
|
||||||
|
setElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setElements() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshData() {
|
||||||
|
// adapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder {
|
||||||
|
|
||||||
|
TextView itemDescription;
|
||||||
|
Button itemValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RileyLinkCommandListAdapter extends BaseAdapter {
|
||||||
|
|
||||||
|
private List<CommandValueDefinition> commandValueList;
|
||||||
|
private Map<CommandValueDefinitionType, CommandValueDefinition> commandValueMap;
|
||||||
|
private LayoutInflater mInflator;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkCommandListAdapter() {
|
||||||
|
super();
|
||||||
|
commandValueList = new ArrayList<>();
|
||||||
|
mInflator = RileyLinkStatusDevice.this.getLayoutInflater();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addItems(List<CommandValueDefinition> list) {
|
||||||
|
commandValueList.addAll(list);
|
||||||
|
|
||||||
|
for (CommandValueDefinition commandValueDefinition : list) {
|
||||||
|
commandValueMap.put(commandValueDefinition.definitionType, commandValueDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CommandValueDefinition getCommandValueItem(int position) {
|
||||||
|
return commandValueList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
commandValueList.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return commandValueList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int i) {
|
||||||
|
return commandValueList.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int i) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||||
|
RileyLinkStatusDevice.ViewHolder viewHolder;
|
||||||
|
// General ListView optimization code.
|
||||||
|
if (view == null) {
|
||||||
|
view = mInflator.inflate(R.layout.rileylink_status_device_item, null);
|
||||||
|
viewHolder = new RileyLinkStatusDevice.ViewHolder();
|
||||||
|
viewHolder.itemDescription = (TextView)view.findViewById(R.id.rileylink_device_label);
|
||||||
|
viewHolder.itemValue = (Button)view.findViewById(R.id.rileylink_device_action);
|
||||||
|
view.setTag(viewHolder);
|
||||||
|
} else {
|
||||||
|
viewHolder = (RileyLinkStatusDevice.ViewHolder)view.getTag();
|
||||||
|
}
|
||||||
|
// Z
|
||||||
|
// RLHistoryItem item = historyItemList.get(i);
|
||||||
|
// viewHolder.itemTime.setText(StringUtil.toDateTimeString(item.getDateTime()));
|
||||||
|
// viewHolder.itemSource.setText("Riley Link"); // for now
|
||||||
|
// viewHolder.itemDescription.setText(item.getDescription());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkStatusGeneral extends Fragment implements RefreshableInterface {
|
||||||
|
|
||||||
|
TextView connectionStatus;
|
||||||
|
TextView configuredAddress;
|
||||||
|
TextView connectedDevice;
|
||||||
|
TextView connectionError;
|
||||||
|
TextView deviceType;
|
||||||
|
TextView deviceModel;
|
||||||
|
TextView serialNumber;
|
||||||
|
TextView pumpFrequency;
|
||||||
|
TextView lastUsedFrequency;
|
||||||
|
TextView lastDeviceContact;
|
||||||
|
TextView firmwareVersion;
|
||||||
|
|
||||||
|
RileyLinkServiceData rileyLinkServiceData;
|
||||||
|
|
||||||
|
MedtronicPumpStatus medtronicPumpStatus;
|
||||||
|
boolean first = false;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.rileylink_status_general, container, false);
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||||
|
|
||||||
|
this.connectionStatus = getActivity().findViewById(R.id.rls_t1_connection_status);
|
||||||
|
this.configuredAddress = getActivity().findViewById(R.id.rls_t1_configured_address);
|
||||||
|
this.connectedDevice = getActivity().findViewById(R.id.rls_t1_connected_device);
|
||||||
|
this.connectionError = getActivity().findViewById(R.id.rls_t1_connection_error);
|
||||||
|
this.deviceType = getActivity().findViewById(R.id.rls_t1_device_type);
|
||||||
|
this.deviceModel = getActivity().findViewById(R.id.rls_t1_device_model);
|
||||||
|
this.serialNumber = getActivity().findViewById(R.id.rls_t1_serial_number);
|
||||||
|
this.pumpFrequency = getActivity().findViewById(R.id.rls_t1_pump_frequency);
|
||||||
|
this.lastUsedFrequency = getActivity().findViewById(R.id.rls_t1_last_used_frequency);
|
||||||
|
this.lastDeviceContact = getActivity().findViewById(R.id.rls_t1_last_device_contact);
|
||||||
|
this.firmwareVersion = getActivity().findViewById(R.id.rls_t1_firmware_version);
|
||||||
|
|
||||||
|
if (!first) {
|
||||||
|
|
||||||
|
// 7-12
|
||||||
|
int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, //
|
||||||
|
R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12, R.id.rls_t1_tv13};
|
||||||
|
|
||||||
|
for (int id : ids) {
|
||||||
|
|
||||||
|
TextView tv = (TextView) getActivity().findViewById(id);
|
||||||
|
tv.setText(tv.getText() + ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
first = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void refreshData() {
|
||||||
|
|
||||||
|
RileyLinkTargetDevice targetDevice = RileyLinkUtil.getTargetDevice();
|
||||||
|
|
||||||
|
this.connectionStatus.setText(MainApp.gs(RileyLinkUtil.getServiceState().getResourceId(targetDevice)));
|
||||||
|
|
||||||
|
if (rileyLinkServiceData != null) {
|
||||||
|
this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress);
|
||||||
|
this.connectionError.setText(rileyLinkServiceData.errorCode == null ? //
|
||||||
|
"-"
|
||||||
|
: MainApp.gs(rileyLinkServiceData.errorCode.getResourceId(targetDevice)));
|
||||||
|
|
||||||
|
|
||||||
|
RileyLinkFirmwareVersion firmwareVersion = rileyLinkServiceData.versionCC110;
|
||||||
|
|
||||||
|
if (firmwareVersion==null) {
|
||||||
|
this.firmwareVersion.setText("BLE113: -\nCC110: -");
|
||||||
|
} else {
|
||||||
|
this.firmwareVersion.setText("BLE113: " + rileyLinkServiceData.versionBLE113 + //
|
||||||
|
"\nCC110: " + firmwareVersion.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add handling for Omnipod pump status
|
||||||
|
this.medtronicPumpStatus = MedtronicUtil.getPumpStatus();
|
||||||
|
|
||||||
|
if (medtronicPumpStatus != null) {
|
||||||
|
this.deviceType.setText(MainApp.gs(RileyLinkUtil.getTargetDevice().getResourceId()));
|
||||||
|
this.deviceModel.setText(medtronicPumpStatus.pumpType.getDescription());
|
||||||
|
this.serialNumber.setText(medtronicPumpStatus.serialNumber);
|
||||||
|
this.pumpFrequency.setText(medtronicPumpStatus.pumpFrequency);
|
||||||
|
|
||||||
|
// TODO extend when Omnipod used
|
||||||
|
|
||||||
|
if (MedtronicUtil.getMedtronicPumpModel() != null)
|
||||||
|
this.connectedDevice.setText("Medtronic " + MedtronicUtil.getMedtronicPumpModel().getPumpModel());
|
||||||
|
else
|
||||||
|
this.connectedDevice.setText("???");
|
||||||
|
|
||||||
|
if (rileyLinkServiceData.lastGoodFrequency != null)
|
||||||
|
this.lastUsedFrequency.setText(String.format(Locale.ENGLISH, "%.2f MHz",
|
||||||
|
rileyLinkServiceData.lastGoodFrequency));
|
||||||
|
|
||||||
|
if (medtronicPumpStatus.lastConnection != 0)
|
||||||
|
this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime(
|
||||||
|
medtronicPumpStatus.lastDataTime)));
|
||||||
|
else
|
||||||
|
this.lastDeviceContact.setText("Never");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/19/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkStatusHistory extends Fragment implements RefreshableInterface {
|
||||||
|
|
||||||
|
RecyclerView recyclerView;
|
||||||
|
RecyclerViewAdapter recyclerViewAdapter;
|
||||||
|
|
||||||
|
LinearLayoutManager llm;
|
||||||
|
List<RLHistoryItem> filteredHistoryList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.rileylink_status_history, container, false);
|
||||||
|
|
||||||
|
recyclerView = (RecyclerView)rootView.findViewById(R.id.rileylink_history_list);
|
||||||
|
|
||||||
|
recyclerView.setHasFixedSize(true);
|
||||||
|
llm = new LinearLayoutManager(getActivity().getApplicationContext());
|
||||||
|
recyclerView.setLayoutManager(llm);
|
||||||
|
|
||||||
|
recyclerViewAdapter = new RecyclerViewAdapter(filteredHistoryList);
|
||||||
|
recyclerView.setAdapter(recyclerViewAdapter);
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshData() {
|
||||||
|
if (RileyLinkUtil.getRileyLinkHistory()!=null) {
|
||||||
|
recyclerViewAdapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.HistoryViewHolder> {
|
||||||
|
|
||||||
|
List<RLHistoryItem> historyList;
|
||||||
|
|
||||||
|
|
||||||
|
RecyclerViewAdapter(List<RLHistoryItem> historyList) {
|
||||||
|
this.historyList = historyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setHistoryList(List<RLHistoryItem> historyList) {
|
||||||
|
this.historyList = historyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addItemsAndClean(List<RLHistoryItem> items) {
|
||||||
|
this.historyList.clear();
|
||||||
|
|
||||||
|
for (RLHistoryItem item : items) {
|
||||||
|
|
||||||
|
if (!historyList.contains(item) && isValidItem(item)) {
|
||||||
|
historyList.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isValidItem(RLHistoryItem item) {
|
||||||
|
|
||||||
|
PumpDeviceState pumpState = item.getPumpDeviceState();
|
||||||
|
|
||||||
|
if ((pumpState != null) && //
|
||||||
|
(pumpState == PumpDeviceState.Sleeping || //
|
||||||
|
pumpState == PumpDeviceState.Active || //
|
||||||
|
pumpState == PumpDeviceState.WakingUp //
|
||||||
|
))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerViewAdapter.HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||||
|
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rileylink_status_history_item, //
|
||||||
|
viewGroup, false);
|
||||||
|
return new RecyclerViewAdapter.HistoryViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerViewAdapter.HistoryViewHolder holder, int position) {
|
||||||
|
RLHistoryItem item = historyList.get(position);
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
holder.timeView.setText(StringUtil.toDateTimeString(item.getDateTime()));
|
||||||
|
holder.typeView.setText(item.getSource().getDesc());
|
||||||
|
holder.valueView.setText(item.getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return historyList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HistoryViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
TextView timeView;
|
||||||
|
TextView typeView;
|
||||||
|
TextView valueView;
|
||||||
|
|
||||||
|
|
||||||
|
HistoryViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
timeView = (TextView)itemView.findViewById(R.id.rileylink_history_time);
|
||||||
|
typeView = (TextView)itemView.findViewById(R.id.rileylink_history_source);
|
||||||
|
valueView = (TextView)itemView.findViewById(R.id.rileylink_history_description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.interfaces.PumpInterface;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
|
||||||
|
public class RileyLinkBluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private static Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
final String action = intent.getAction();
|
||||||
|
|
||||||
|
PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump();
|
||||||
|
|
||||||
|
if (action != null && activePump != null) {
|
||||||
|
|
||||||
|
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
|
||||||
|
switch (state) {
|
||||||
|
case BluetoothAdapter.STATE_OFF:
|
||||||
|
case BluetoothAdapter.STATE_TURNING_OFF:
|
||||||
|
case BluetoothAdapter.STATE_TURNING_ON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BluetoothAdapter.STATE_ON: {
|
||||||
|
LOG.debug("RileyLinkBluetoothStateReceiver: Bluetooth back on. Sending broadcast to RileyLink Framework");
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothReconnected);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void unregisterBroadcasts() {
|
||||||
|
MainApp.instance().unregisterReceiver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void registerBroadcasts() {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
|
||||||
|
MainApp.instance().registerReceiver(this, filter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,260 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 10/23/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.DiscoverGattServicesTask;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.InitializePumpManagerTask;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I added this class outside of RileyLinkService, because for now it's very important part of RL framework and
|
||||||
|
* where we get a lot of problems. Especially merging between AAPS and RileyLinkAAPS. I might put it back at
|
||||||
|
* later time
|
||||||
|
*/
|
||||||
|
public class RileyLinkBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||||
|
|
||||||
|
RileyLinkService serviceInstance;
|
||||||
|
protected Map<String, List<String>> broadcastIdentifiers = null;
|
||||||
|
String deviceSpecificPrefix;
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkBroadcastReceiver(RileyLinkService serviceInstance, Context context) {
|
||||||
|
this.serviceInstance = serviceInstance;
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
createBroadcastIdentifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void createBroadcastIdentifiers() {
|
||||||
|
|
||||||
|
this.broadcastIdentifiers = new HashMap<>();
|
||||||
|
|
||||||
|
// Bluetooth
|
||||||
|
this.broadcastIdentifiers.put("Bluetooth", Arrays.asList( //
|
||||||
|
RileyLinkConst.Intents.BluetoothConnected, //
|
||||||
|
RileyLinkConst.Intents.BluetoothReconnected));
|
||||||
|
|
||||||
|
// TuneUp
|
||||||
|
this.broadcastIdentifiers.put("TuneUp", Arrays.asList( //
|
||||||
|
RileyLinkConst.IPC.MSG_PUMP_tunePump, //
|
||||||
|
RileyLinkConst.IPC.MSG_PUMP_quickTune));
|
||||||
|
|
||||||
|
// RileyLink
|
||||||
|
this.broadcastIdentifiers.put("RileyLink", Arrays.asList( //
|
||||||
|
RileyLinkConst.Intents.RileyLinkDisconnected, //
|
||||||
|
RileyLinkConst.Intents.RileyLinkReady, //
|
||||||
|
RileyLinkConst.Intents.RileyLinkDisconnected, //
|
||||||
|
RileyLinkConst.Intents.RileyLinkNewAddressSet, //
|
||||||
|
RileyLinkConst.Intents.RileyLinkDisconnect));
|
||||||
|
|
||||||
|
// Device Specific
|
||||||
|
deviceSpecificPrefix = serviceInstance.getDeviceSpecificBroadcastsIdentifierPrefix();
|
||||||
|
|
||||||
|
// Application specific
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
LOG.error("onReceive: received null intent");
|
||||||
|
} else {
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (action == null) {
|
||||||
|
LOG.error("onReceive: null action");
|
||||||
|
} else {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.debug("Received Broadcast: " + action);
|
||||||
|
|
||||||
|
if (!processBluetoothBroadcasts(action) && //
|
||||||
|
!processRileyLinkBroadcasts(action) && //
|
||||||
|
!processTuneUpBroadcasts(action) && //
|
||||||
|
!processDeviceSpecificBroadcasts(action, intent) && //
|
||||||
|
!processApplicationSpecificBroadcasts(action, intent) //
|
||||||
|
) {
|
||||||
|
LOG.error("Unhandled broadcast: action=" + action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void registerBroadcasts() {
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> stringListEntry : broadcastIdentifiers.entrySet()) {
|
||||||
|
|
||||||
|
for (String intentKey : stringListEntry.getValue()) {
|
||||||
|
intentFilter.addAction(intentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceSpecificPrefix != null) {
|
||||||
|
serviceInstance.registerDeviceSpecificBroadcasts(intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean processRileyLinkBroadcasts(String action) {
|
||||||
|
|
||||||
|
if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnected)) {
|
||||||
|
if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
|
||||||
|
RileyLinkUtil
|
||||||
|
.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.RileyLinkUnreachable);
|
||||||
|
} else {
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (action.equals(RileyLinkConst.Intents.RileyLinkReady)) {
|
||||||
|
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.warn("MedtronicConst.Intents.RileyLinkReady");
|
||||||
|
// sendIPCNotification(RT2Const.IPC.MSG_note_WakingPump);
|
||||||
|
|
||||||
|
if (this.serviceInstance.rileyLinkBLE == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.serviceInstance.rileyLinkBLE.enableNotifications();
|
||||||
|
this.serviceInstance.rfspy.startReader(); // call startReader from outside?
|
||||||
|
|
||||||
|
this.serviceInstance.rfspy.initializeRileyLink();
|
||||||
|
String bleVersion = this.serviceInstance.rfspy.getBLEVersionCached();
|
||||||
|
RileyLinkFirmwareVersion rlVersion = this.serviceInstance.rfspy.getRLVersionCached();
|
||||||
|
|
||||||
|
// if (isLoggingEnabled())
|
||||||
|
LOG.debug("RfSpy version (BLE113): " + bleVersion);
|
||||||
|
this.serviceInstance.rileyLinkServiceData.versionBLE113 = bleVersion;
|
||||||
|
|
||||||
|
// if (isLoggingEnabled())
|
||||||
|
LOG.debug("RfSpy Radio version (CC110): " + rlVersion.name());
|
||||||
|
this.serviceInstance.rileyLinkServiceData.versionCC110 = rlVersion;
|
||||||
|
|
||||||
|
ServiceTask task = new InitializePumpManagerTask(RileyLinkUtil.getTargetDevice());
|
||||||
|
ServiceTaskExecutor.startTask(task);
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.info("Announcing RileyLink open For business");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (action.equals(RileyLinkConst.Intents.RileyLinkNewAddressSet)) {
|
||||||
|
String RileylinkBLEAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, "");
|
||||||
|
if (RileylinkBLEAddress.equals("")) {
|
||||||
|
LOG.error("No Rileylink BLE Address saved in app");
|
||||||
|
} else {
|
||||||
|
// showBusy("Configuring Service", 50);
|
||||||
|
// rileyLinkBLE.findRileyLink(RileylinkBLEAddress);
|
||||||
|
this.serviceInstance.reconfigureRileyLink(RileylinkBLEAddress);
|
||||||
|
// MainApp.getServiceClientConnection().setThisRileylink(RileylinkBLEAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnect)) {
|
||||||
|
this.serviceInstance.disconnectRileyLink();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean processBluetoothBroadcasts(String action) {
|
||||||
|
|
||||||
|
if (action.equals(RileyLinkConst.Intents.BluetoothConnected)) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.debug("Bluetooth - Connected");
|
||||||
|
ServiceTaskExecutor.startTask(new DiscoverGattServicesTask());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if (action.equals(RileyLinkConst.Intents.BluetoothReconnected)) {
|
||||||
|
if (isLoggingEnabled())
|
||||||
|
LOG.debug("Bluetooth - Reconnecting");
|
||||||
|
|
||||||
|
serviceInstance.bluetoothInit();
|
||||||
|
ServiceTaskExecutor.startTask(new DiscoverGattServicesTask(true));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean processTuneUpBroadcasts(String action) {
|
||||||
|
|
||||||
|
if (this.broadcastIdentifiers.get("TuneUp").contains(action)) {
|
||||||
|
if (serviceInstance.getRileyLinkTargetDevice().isTuneUpEnabled()) {
|
||||||
|
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean processDeviceSpecificBroadcasts(String action, Intent intent) {
|
||||||
|
|
||||||
|
if (this.deviceSpecificPrefix == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.startsWith(this.deviceSpecificPrefix)) {
|
||||||
|
return this.serviceInstance.handleDeviceSpecificBroadcasts(intent);
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean processApplicationSpecificBroadcasts(String action, Intent intent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isLoggingEnabled() {
|
||||||
|
return (L.isEnabled(L.PUMPCOMM));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterBroadcasts() {
|
||||||
|
LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,289 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||||
|
|
||||||
|
import static info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil.getRileyLinkCommunicationManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/6/18.
|
||||||
|
* Split from original file and renamed.
|
||||||
|
*/
|
||||||
|
public abstract class RileyLinkService extends Service {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||||
|
|
||||||
|
public RileyLinkBLE rileyLinkBLE; // android-bluetooth management
|
||||||
|
protected BluetoothAdapter bluetoothAdapter;
|
||||||
|
protected RFSpy rfspy; // interface for RL xxx Mhz radio.
|
||||||
|
protected Context context;
|
||||||
|
protected RileyLinkBroadcastReceiver mBroadcastReceiver;
|
||||||
|
protected RileyLinkServiceData rileyLinkServiceData;
|
||||||
|
protected RileyLinkBluetoothStateReceiver bluetoothStateReceiver;
|
||||||
|
|
||||||
|
public RileyLinkService(Context context) {
|
||||||
|
super();
|
||||||
|
this.context = context;
|
||||||
|
RileyLinkUtil.setContext(this.context);
|
||||||
|
RileyLinkUtil.setRileyLinkService(this);
|
||||||
|
RileyLinkUtil.setEncoding(getEncoding());
|
||||||
|
initRileyLinkServiceData();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Encoding for RileyLink communication
|
||||||
|
*/
|
||||||
|
public abstract RileyLinkEncodingType getEncoding();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you have customized RileyLinkServiceData you need to override this
|
||||||
|
*/
|
||||||
|
public abstract void initRileyLinkServiceData();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
//LOG.warn("onUnbind");
|
||||||
|
return super.onUnbind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRebind(Intent intent) {
|
||||||
|
//LOG.warn("onRebind");
|
||||||
|
super.onRebind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
//LOG.error("I die! I die!");
|
||||||
|
|
||||||
|
if (rileyLinkBLE != null) {
|
||||||
|
rileyLinkBLE.disconnect(); // dispose of Gatt (disconnect and close)
|
||||||
|
rileyLinkBLE = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBroadcastReceiver!=null) {
|
||||||
|
mBroadcastReceiver.unregisterBroadcasts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bluetoothStateReceiver!=null) {
|
||||||
|
bluetoothStateReceiver.unregisterBroadcasts();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
//LOG.debug("onCreate");
|
||||||
|
|
||||||
|
mBroadcastReceiver = new RileyLinkBroadcastReceiver(this, this.context);
|
||||||
|
mBroadcastReceiver.registerBroadcasts();
|
||||||
|
|
||||||
|
|
||||||
|
bluetoothStateReceiver = new RileyLinkBluetoothStateReceiver();
|
||||||
|
bluetoothStateReceiver.registerBroadcasts();
|
||||||
|
|
||||||
|
//LOG.debug("onCreate(): It's ALIVE!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix for Device specific broadcast identifier prefix (for example MSG_PUMP_ for pump or
|
||||||
|
* MSG_POD_ for Omnipod)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public abstract String getDeviceSpecificBroadcastsIdentifierPrefix();
|
||||||
|
|
||||||
|
|
||||||
|
public abstract boolean handleDeviceSpecificBroadcasts(Intent intent);
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void registerDeviceSpecificBroadcasts(IntentFilter intentFilter);
|
||||||
|
|
||||||
|
|
||||||
|
public abstract RileyLinkCommunicationManager getDeviceCommunicationManager();
|
||||||
|
|
||||||
|
|
||||||
|
// Here is where the wake-lock begins:
|
||||||
|
// We've received a service startCommand, we grab the lock.
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
RileyLinkUtil.setContext(getApplicationContext());
|
||||||
|
return (START_STICKY);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean bluetoothInit() {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("bluetoothInit: attempting to get an adapter");
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing);
|
||||||
|
|
||||||
|
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
|
||||||
|
if (bluetoothAdapter == null) {
|
||||||
|
LOG.error("Unable to obtain a BluetoothAdapter.");
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.NoBluetoothAdapter);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (!bluetoothAdapter.isEnabled()) {
|
||||||
|
LOG.error("Bluetooth is not enabled.");
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled);
|
||||||
|
} else {
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// returns true if our Rileylink configuration changed
|
||||||
|
public boolean reconfigureRileyLink(String deviceAddress) {
|
||||||
|
|
||||||
|
if (rileyLinkBLE == null) {
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkInitializing);
|
||||||
|
|
||||||
|
if (rileyLinkBLE.isConnected()) {
|
||||||
|
if (deviceAddress.equals(rileyLinkServiceData.rileylinkAddress)) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("No change to RL address. Not reconnecting.");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.warn("Disconnecting from old RL (" + rileyLinkServiceData.rileylinkAddress
|
||||||
|
+ "), reconnecting to new: " + deviceAddress);
|
||||||
|
|
||||||
|
rileyLinkBLE.disconnect();
|
||||||
|
// prolly need to shut down listening thread too?
|
||||||
|
// SP.putString(MedtronicConst.Prefs.RileyLinkAddress, deviceAddress);
|
||||||
|
|
||||||
|
rileyLinkServiceData.rileylinkAddress = deviceAddress;
|
||||||
|
rileyLinkBLE.findRileyLink(rileyLinkServiceData.rileylinkAddress);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.debug("Using RL " + deviceAddress);
|
||||||
|
|
||||||
|
if (RileyLinkUtil.getServiceState() == RileyLinkServiceState.NotStarted) {
|
||||||
|
if (!bluetoothInit()) {
|
||||||
|
LOG.error("RileyLink can't get activated, Bluetooth is not functioning correctly. {}",
|
||||||
|
RileyLinkUtil.getError() != null ? RileyLinkUtil.getError().name() : "Unknown error (null)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rileyLinkBLE.findRileyLink(deviceAddress);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc.
|
||||||
|
public void doTuneUpDevice() {
|
||||||
|
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.TuneUpDevice);
|
||||||
|
MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping);
|
||||||
|
|
||||||
|
double lastGoodFrequency = 0.0d;
|
||||||
|
|
||||||
|
if (rileyLinkServiceData.lastGoodFrequency == null) {
|
||||||
|
lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d);
|
||||||
|
} else {
|
||||||
|
lastGoodFrequency = rileyLinkServiceData.lastGoodFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
double newFrequency;
|
||||||
|
|
||||||
|
newFrequency = getDeviceCommunicationManager().tuneForDevice();
|
||||||
|
|
||||||
|
if ((newFrequency != 0.0) && (newFrequency != lastGoodFrequency)) {
|
||||||
|
if (isLogEnabled())
|
||||||
|
LOG.info("Saving new pump frequency of {} MHz", newFrequency);
|
||||||
|
SP.putDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, newFrequency);
|
||||||
|
rileyLinkServiceData.lastGoodFrequency = newFrequency;
|
||||||
|
rileyLinkServiceData.tuneUpDone = true;
|
||||||
|
rileyLinkServiceData.lastTuneUpTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFrequency == 0.0d) {
|
||||||
|
// error tuning pump, pump not present ??
|
||||||
|
RileyLinkUtil
|
||||||
|
.setServiceState(RileyLinkServiceState.PumpConnectorError, RileyLinkError.TuneUpOfDeviceFailed);
|
||||||
|
} else {
|
||||||
|
getRileyLinkCommunicationManager().clearNotConnectedCount();
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void disconnectRileyLink() {
|
||||||
|
|
||||||
|
if (this.rileyLinkBLE != null && this.rileyLinkBLE.isConnected()) {
|
||||||
|
this.rileyLinkBLE.disconnect();
|
||||||
|
rileyLinkServiceData.rileylinkAddress = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Target Device for Service
|
||||||
|
*/
|
||||||
|
public RileyLinkTargetDevice getRileyLinkTargetDevice() {
|
||||||
|
return this.rileyLinkServiceData.targetDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isLogEnabled() {
|
||||||
|
return L.isEnabled(L.PUMPCOMM);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void changeRileyLinkEncoding(RileyLinkEncodingType encodingType) {
|
||||||
|
if (rfspy != null) {
|
||||||
|
rfspy.setRileyLinkEncoding(encodingType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 16/05/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkServiceData {
|
||||||
|
|
||||||
|
public boolean tuneUpDone = false;
|
||||||
|
public RileyLinkError errorCode;
|
||||||
|
public RileyLinkServiceState serviceState = RileyLinkServiceState.NotStarted;
|
||||||
|
public String rileylinkAddress;
|
||||||
|
public long lastTuneUpTime = 0L;
|
||||||
|
public Double lastGoodFrequency;
|
||||||
|
|
||||||
|
// bt version
|
||||||
|
public String versionBLE113;
|
||||||
|
// radio version
|
||||||
|
public RileyLinkFirmwareVersion versionCC110;
|
||||||
|
|
||||||
|
public RileyLinkTargetDevice targetDevice;
|
||||||
|
|
||||||
|
// Medtronic Pump
|
||||||
|
public String pumpID;
|
||||||
|
public byte[] pumpIDBytes;
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkServiceData(RileyLinkTargetDevice targetDevice) {
|
||||||
|
this.targetDevice = targetDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setPumpID(String pumpId, byte[] pumpIdBytes) {
|
||||||
|
this.pumpID = pumpId;
|
||||||
|
this.pumpIDBytes = pumpIdBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/25/16.
|
||||||
|
*/
|
||||||
|
public class ServiceCommand extends ServiceMessage {
|
||||||
|
|
||||||
|
public ServiceCommand() {
|
||||||
|
map = new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// commandID is a string that the client can set on the message.
|
||||||
|
// The service does not use this value, but passes it back with the result
|
||||||
|
// so that the client can identify it.
|
||||||
|
public ServiceCommand(String commandName, String commandID) {
|
||||||
|
init();
|
||||||
|
map.putString("command", commandName);
|
||||||
|
map.putString("commandID", commandID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceCommand(Bundle commandBundle) {
|
||||||
|
if (commandBundle != null) {
|
||||||
|
map = commandBundle;
|
||||||
|
} else {
|
||||||
|
map = new Bundle();
|
||||||
|
init();
|
||||||
|
map.putString("command", "(null)");
|
||||||
|
map.putString("commandID", "(null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
map.putString("ServiceMessageType", "ServiceCommand");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getCommandID() {
|
||||||
|
return map.getString("commandID");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getCommandName() {
|
||||||
|
return map.getString("command");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isPumpCommand() {
|
||||||
|
switch (getCommandName()) {
|
||||||
|
case "FetchPumpHistory":
|
||||||
|
case "ReadPumpClock":
|
||||||
|
case "RetrieveHistoryPage":
|
||||||
|
case "ReadISFProfile":
|
||||||
|
case "ReadBolusWizardCarbProfile":
|
||||||
|
case "UpdatePumpStatus":
|
||||||
|
case "WakeAndTune":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/4/16.
|
||||||
|
* <p>
|
||||||
|
* Base class for all messages passed between service and client
|
||||||
|
*/
|
||||||
|
public class ServiceMessage {
|
||||||
|
|
||||||
|
protected Bundle map = new Bundle();
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceMessage() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
map.putString("ServiceMessageClass", this.getClass().getCanonicalName());
|
||||||
|
map.putString("ServiceMessageType", this.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Bundle getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setMap(Bundle map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getServiceMessageType() {
|
||||||
|
return map.getString("ServiceMessageType");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/6/16.
|
||||||
|
* <p>
|
||||||
|
* These are "one liner" messages between client and service. Must still be contained within ServiceTransports
|
||||||
|
*/
|
||||||
|
public class ServiceNotification extends ServiceMessage {
|
||||||
|
|
||||||
|
public ServiceNotification() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceNotification(Bundle b) {
|
||||||
|
if (b != null) {
|
||||||
|
if ("ServiceNotification".equals(b.getString("ServiceMessageType"))) {
|
||||||
|
setMap(b);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceNotification(String notificationType) {
|
||||||
|
setNotificationType(notificationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
super.init();
|
||||||
|
map.putString("ServiceMessageType", "ServiceNotification");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getNotificationType() {
|
||||||
|
return map.getString("NotificationType", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setNotificationType(String notificationType) {
|
||||||
|
map.putString("NotificationType", notificationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/25/16.
|
||||||
|
*/
|
||||||
|
public class ServiceResult extends ServiceMessage {
|
||||||
|
|
||||||
|
public static final int ERROR_MALFORMED_PUMP_RESPONSE = 1;
|
||||||
|
public static final int ERROR_NULL_PUMP_RESPONSE = 2;
|
||||||
|
public static final int ERROR_INVALID_PUMP_RESPONSE = 3;
|
||||||
|
public static final int ERROR_PUMP_BUSY = 4;
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceResult() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceResult(Bundle resultBundle) {
|
||||||
|
if (resultBundle != null) {
|
||||||
|
setMap(resultBundle);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final String getErrorDescription(int errorCode) {
|
||||||
|
switch (errorCode) {
|
||||||
|
case ERROR_MALFORMED_PUMP_RESPONSE:
|
||||||
|
return "Malformed Pump Response";
|
||||||
|
case ERROR_NULL_PUMP_RESPONSE:
|
||||||
|
return "Null pump response";
|
||||||
|
case ERROR_INVALID_PUMP_RESPONSE:
|
||||||
|
return "Invalid pump response";
|
||||||
|
case ERROR_PUMP_BUSY:
|
||||||
|
return "A pump command session is already in progress";
|
||||||
|
default:
|
||||||
|
return "Unknown error code (" + errorCode + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
super.init();
|
||||||
|
map.putString("ServiceMessageType", "ServiceResult");
|
||||||
|
setServiceResultType(this.getClass().getSimpleName());
|
||||||
|
setResultError(0, "Uninitialized ServiceResult");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getServiceResultType() {
|
||||||
|
return map.getString("ServiceResultType", "ServiceResult");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setServiceResultType(String serviceResultType) {
|
||||||
|
map.putString("ServiceResultType", serviceResultType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setResultOK() {
|
||||||
|
map.putString("result", "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setResultError(int errorCode) {
|
||||||
|
setResultError(errorCode, getErrorDescription(errorCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setResultError(int errorCode, String errorDescription) {
|
||||||
|
map.putString("result", "error");
|
||||||
|
map.putInt("errorCode", errorCode);
|
||||||
|
map.putString("errorDescription", errorDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean resultIsOK() {
|
||||||
|
return ("OK".equals(map.getString("result", "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getErrorDescription() {
|
||||||
|
return map.getString("errorDescription", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
return map.getString("result", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/6/16.
|
||||||
|
* <p>
|
||||||
|
* This class exists to hold a ServiceCommand along with transport variables such as time sent, time received, sender.
|
||||||
|
* May also contain result, if the command is completed.
|
||||||
|
*/
|
||||||
|
public class ServiceTransport extends ServiceMessage {
|
||||||
|
|
||||||
|
private ServiceTransportType serviceTransportType = ServiceTransportType.Undefined;
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTransport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTransport(Bundle b) {
|
||||||
|
if (b != null) {
|
||||||
|
if ("ServiceTransport".equals(b.getString("ServiceMessageType"))) {
|
||||||
|
setMap(b);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
super.init();
|
||||||
|
map.putString("ServiceMessageType", "ServiceTransport");
|
||||||
|
setTransportType("unknown");
|
||||||
|
setSenderHashcode(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Integer getSenderHashcode() {
|
||||||
|
return map.getInt("senderHashCode", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setSenderHashcode(Integer senderHashcode) {
|
||||||
|
map.putInt("senderHashcode", senderHashcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceCommand getServiceCommand() {
|
||||||
|
return new ServiceCommand(map.getBundle("ServiceCommand"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setServiceCommand(ServiceCommand serviceCommand) {
|
||||||
|
map.putBundle("ServiceCommand", serviceCommand.getMap());
|
||||||
|
this.serviceTransportType = ServiceTransportType.ServiceCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasServiceCommand() {
|
||||||
|
return (getMap().containsKey("ServiceCommand"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getTransportType() {
|
||||||
|
return map.getString("transportType", "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// On remote end, this will be converted to the "action" of a local Intent,
|
||||||
|
// so can be used for separating types of messages to different internal handlers.
|
||||||
|
public void setTransportType(String transportType) {
|
||||||
|
map.putString("transportType", transportType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceResult getServiceResult() {
|
||||||
|
return new ServiceResult(map.getBundle("ServiceResult"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setServiceResult(ServiceResult serviceResult) {
|
||||||
|
map.putBundle("ServiceResult", serviceResult.getMap());
|
||||||
|
this.serviceTransportType = ServiceTransportType.ServiceResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasServiceResult() {
|
||||||
|
return (getMap().containsKey("ServiceResult"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceNotification getServiceNotification() {
|
||||||
|
return new ServiceNotification(map.getBundle("ServiceNotification"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setServiceNotification(ServiceNotification notification) {
|
||||||
|
map.putBundle("ServiceNotification", notification.getMap());
|
||||||
|
this.serviceTransportType = ServiceTransportType.ServiceNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasServiceNotification() {
|
||||||
|
return (map.containsKey("ServiceNotification"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean commandDidCompleteOK() {
|
||||||
|
return getServiceResult().resultIsOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getOriginalCommandName() {
|
||||||
|
return getServiceCommand().getCommandName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String describeContentsShort() {
|
||||||
|
String rval = "";
|
||||||
|
rval += getTransportType();
|
||||||
|
|
||||||
|
if (this.serviceTransportType == ServiceTransportType.ServiceNotification) {
|
||||||
|
rval += "note: " + getServiceNotification().getNotificationType();
|
||||||
|
} else if (this.serviceTransportType == ServiceTransportType.ServiceCommand) {
|
||||||
|
rval += ", cmd=" + getOriginalCommandName();
|
||||||
|
} else if (this.serviceTransportType == ServiceTransportType.ServiceResult) {
|
||||||
|
rval += ", cmd=" + getOriginalCommandName();
|
||||||
|
rval += ", rslt=" + getServiceResult().getResult();
|
||||||
|
rval += ", err=" + getServiceResult().getErrorDescription();
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTransport clone() {
|
||||||
|
Parcel p = Parcel.obtain();
|
||||||
|
Parcel p2 = Parcel.obtain();
|
||||||
|
getMap().writeToParcel(p, 0);
|
||||||
|
byte[] bytes = p.marshall();
|
||||||
|
p2.unmarshall(bytes, 0, bytes.length);
|
||||||
|
p2.setDataPosition(0);
|
||||||
|
Bundle b = p2.readBundle();
|
||||||
|
ServiceTransport rval = new ServiceTransport();
|
||||||
|
rval.setMap(b);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 31/05/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum ServiceTransportType {
|
||||||
|
|
||||||
|
Undefined, //
|
||||||
|
ServiceNotification, //
|
||||||
|
|
||||||
|
ServiceCommand, //
|
||||||
|
ServiceResult;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/9/16.
|
||||||
|
*/
|
||||||
|
public class DiscoverGattServicesTask extends ServiceTask {
|
||||||
|
|
||||||
|
public boolean needToConnect = false;
|
||||||
|
|
||||||
|
|
||||||
|
public DiscoverGattServicesTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DiscoverGattServicesTask(boolean needToConnect) {
|
||||||
|
this.needToConnect = needToConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
if (needToConnect)
|
||||||
|
RileyLinkUtil.getRileyLinkBLE().connectGatt();
|
||||||
|
|
||||||
|
RileyLinkUtil.getRileyLinkBLE().discoverServices();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/9/16.
|
||||||
|
* <p>
|
||||||
|
* This class is intended to be run by the Service, for the Service. Not intended for clients to run.
|
||||||
|
*/
|
||||||
|
public class InitializePumpManagerTask extends ServiceTask {
|
||||||
|
|
||||||
|
private static final String TAG = "InitPumpManagerTask";
|
||||||
|
private RileyLinkTargetDevice targetDevice;
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||||
|
|
||||||
|
public InitializePumpManagerTask(RileyLinkTargetDevice targetDevice) {
|
||||||
|
super();
|
||||||
|
this.targetDevice = targetDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public InitializePumpManagerTask(ServiceTransport transport) {
|
||||||
|
super(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
double lastGoodFrequency = 0.0d;
|
||||||
|
|
||||||
|
if (RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency==null) {
|
||||||
|
|
||||||
|
lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d);
|
||||||
|
lastGoodFrequency = Math.round(lastGoodFrequency * 1000d) / 1000d;
|
||||||
|
|
||||||
|
RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency = lastGoodFrequency;
|
||||||
|
|
||||||
|
// if (RileyLinkUtil.getRileyLinkTargetFrequency() == null) {
|
||||||
|
// String pumpFrequency = SP.getString(MedtronicConst.Prefs.PumpFrequency, null);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
lastGoodFrequency = RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((lastGoodFrequency > 0.0d)
|
||||||
|
&& RileyLinkUtil.getRileyLinkCommunicationManager().isValidFrequency(lastGoodFrequency)) {
|
||||||
|
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkReady);
|
||||||
|
|
||||||
|
if (L.isEnabled(L.PUMPCOMM))
|
||||||
|
LOG.info("Setting radio frequency to {} MHz", lastGoodFrequency);
|
||||||
|
|
||||||
|
RileyLinkUtil.getRileyLinkCommunicationManager().setRadioFrequencyForPump(lastGoodFrequency);
|
||||||
|
|
||||||
|
boolean foundThePump = RileyLinkUtil.getRileyLinkCommunicationManager().tryToConnectToDevice();
|
||||||
|
|
||||||
|
if (foundThePump) {
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady);
|
||||||
|
} else {
|
||||||
|
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorError,
|
||||||
|
RileyLinkError.NoContactWithDevice);
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/10/16.
|
||||||
|
*/
|
||||||
|
public class PumpTask extends ServiceTask {
|
||||||
|
|
||||||
|
public PumpTask() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public PumpTask(ServiceTransport transport) {
|
||||||
|
super(transport);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicFragment;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/16/16.
|
||||||
|
*/
|
||||||
|
public class ResetRileyLinkConfigurationTask extends PumpTask {
|
||||||
|
|
||||||
|
private static final String TAG = "ResetRileyLinkTask";
|
||||||
|
|
||||||
|
|
||||||
|
public ResetRileyLinkConfigurationTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ResetRileyLinkConfigurationTask(ServiceTransport transport) {
|
||||||
|
super(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
MedtronicFragment.refreshButtonEnabled(false);
|
||||||
|
MedtronicPumpPlugin.isBusy = true;
|
||||||
|
RileyLinkMedtronicService.getInstance().resetRileyLinkConfiguration();
|
||||||
|
MedtronicPumpPlugin.isBusy = false;
|
||||||
|
MedtronicFragment.refreshButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/9/16.
|
||||||
|
*/
|
||||||
|
public class ServiceTask implements Runnable {
|
||||||
|
|
||||||
|
private static final String TAG = "ServiceTask(base)";
|
||||||
|
public boolean completed = false;
|
||||||
|
protected ServiceTransport mTransport;
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTask() {
|
||||||
|
init(new ServiceTransport());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTask(ServiceTransport transport) {
|
||||||
|
init(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void init(ServiceTransport transport) {
|
||||||
|
mTransport = transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void preOp() {
|
||||||
|
// This function is called by UI thread before running asynch thread.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void postOp() {
|
||||||
|
// This function is called by UI thread after running asynch thread.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServiceTransport getServiceTransport() {
|
||||||
|
return mTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* protected void sendResponse(ServiceResult result) {
|
||||||
|
* RoundtripService.getInstance().sendServiceTransportResponse(mTransport,result);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/9/16.
|
||||||
|
*/
|
||||||
|
public class ServiceTaskExecutor extends ThreadPoolExecutor {
|
||||||
|
|
||||||
|
private static final String TAG = "ServiceTaskExecutor";
|
||||||
|
private static ServiceTaskExecutor instance;
|
||||||
|
private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
instance = new ServiceTaskExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ServiceTaskExecutor() {
|
||||||
|
super(1, 1, 10000, TimeUnit.MILLISECONDS, taskQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ServiceTaskExecutor getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ServiceTask startTask(ServiceTask task) {
|
||||||
|
instance.execute(task); // task will be run on async thread from pool.
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
protected void beforeExecute(Thread t, Runnable r) {
|
||||||
|
// This is run on either caller UI thread or Service UI thread.
|
||||||
|
ServiceTask task = (ServiceTask)r;
|
||||||
|
Log.v(TAG, "About to run task " + task.getClass().getSimpleName());
|
||||||
|
RileyLinkUtil.setCurrentTask(task);
|
||||||
|
task.preOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
|
// This is run on either caller UI thread or Service UI thread.
|
||||||
|
ServiceTask task = (ServiceTask)r;
|
||||||
|
task.postOp();
|
||||||
|
Log.v(TAG, "Finishing task " + task.getClass().getSimpleName());
|
||||||
|
RileyLinkUtil.finishCurrentTask(task);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicFragment;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 7/16/16.
|
||||||
|
*/
|
||||||
|
public class WakeAndTuneTask extends PumpTask {
|
||||||
|
|
||||||
|
private static final String TAG = "WakeAndTuneTask";
|
||||||
|
|
||||||
|
|
||||||
|
public WakeAndTuneTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public WakeAndTuneTask(ServiceTransport transport) {
|
||||||
|
super(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
MedtronicFragment.refreshButtonEnabled(false);
|
||||||
|
MedtronicPumpPlugin.isBusy = true;
|
||||||
|
RileyLinkMedtronicService.getInstance().doTuneUpDevice();
|
||||||
|
MedtronicPumpPlugin.isBusy = false;
|
||||||
|
MedtronicFragment.refreshButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 10/18/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RileyLinkSelectPreference extends Preference {
|
||||||
|
|
||||||
|
public RileyLinkSelectPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
setInitialSummaryValue();
|
||||||
|
|
||||||
|
MedtronicUtil.setRileyLinkSelectPreference(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RileyLinkSelectPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
setInitialSummaryValue();
|
||||||
|
|
||||||
|
MedtronicUtil.setRileyLinkSelectPreference(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setInitialSummaryValue() {
|
||||||
|
String value = SP.getString("pref_rileylink_mac_address", null);
|
||||||
|
|
||||||
|
setSummary(value == null ? MainApp.gs(R.string.rileylink_error_address_not_set_short) : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,450 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 4/28/15.
|
||||||
|
*/
|
||||||
|
public class ByteUtil {
|
||||||
|
|
||||||
|
private final static char[] HEX_DIGITS = {
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||||
|
|
||||||
|
private final static String HEX_DIGITS_STR = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
|
||||||
|
public static byte highByte(short s) {
|
||||||
|
return (byte) (s / 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte lowByte(short s) {
|
||||||
|
return (byte) (s % 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int asUINT8(byte b) {
|
||||||
|
return (b < 0) ? b + 256 : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* For Reference: static void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) */
|
||||||
|
|
||||||
|
public static byte[] concat(byte[] a, byte[] b) {
|
||||||
|
|
||||||
|
if (b == null) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aLen = a.length;
|
||||||
|
int bLen = b.length;
|
||||||
|
byte[] c = new byte[aLen + bLen];
|
||||||
|
System.arraycopy(a, 0, c, 0, aLen);
|
||||||
|
System.arraycopy(b, 0, c, aLen, bLen);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] concat(byte[] a, byte b) {
|
||||||
|
int aLen = a.length;
|
||||||
|
byte[] c = new byte[aLen + 1];
|
||||||
|
System.arraycopy(a, 0, c, 0, aLen);
|
||||||
|
c[aLen] = b;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] concat(byte a, byte[] b) {
|
||||||
|
int aLen = b.length;
|
||||||
|
byte[] c = new byte[aLen + 1];
|
||||||
|
c[0] = a;
|
||||||
|
System.arraycopy(b, 0, c, 1, aLen);
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] substring(byte[] a, int start, int len) {
|
||||||
|
byte[] rval = new byte[len];
|
||||||
|
System.arraycopy(a, start, rval, 0, len);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] substring(List<Byte> a, int start, int len) {
|
||||||
|
byte[] rval = new byte[len];
|
||||||
|
|
||||||
|
for (int i = start, j = 0; i < start + len; i++, j++) {
|
||||||
|
rval[j] = a.get(i);
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] substring(byte[] a, int start) {
|
||||||
|
int len = a.length - start;
|
||||||
|
byte[] rval = new byte[len];
|
||||||
|
System.arraycopy(a, start, rval, 0, len);
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String shortHexString(byte[] ra) {
|
||||||
|
String rval = "";
|
||||||
|
if (ra == null) {
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
if (ra.length == 0) {
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < ra.length; i++) {
|
||||||
|
rval = rval + HEX_DIGITS[(ra[i] & 0xF0) >> 4];
|
||||||
|
rval = rval + HEX_DIGITS[(ra[i] & 0x0F)];
|
||||||
|
if (i < ra.length - 1) {
|
||||||
|
rval = rval + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String shortHexString(List<Byte> list) {
|
||||||
|
|
||||||
|
byte[] abyte0 = getByteArrayFromList(list);
|
||||||
|
|
||||||
|
return shortHexString(abyte0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String shortHexString(byte val) {
|
||||||
|
return getHexCompact(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String showPrintable(byte[] ra) {
|
||||||
|
String s = new String();
|
||||||
|
for (int i = 0; i < ra.length; i++) {
|
||||||
|
char c = (char) ra[i];
|
||||||
|
if (((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) {
|
||||||
|
s = s + c;
|
||||||
|
} else {
|
||||||
|
s = s + '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] fromHexString(String src) {
|
||||||
|
String s = src.toUpperCase();
|
||||||
|
byte[] rval = new byte[]{};
|
||||||
|
if ((s.length() % 2) != 0) {
|
||||||
|
// invalid hex string!
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < s.length(); i += 2) {
|
||||||
|
int highNibbleOrd = HEX_DIGITS_STR.indexOf(s.charAt(i));
|
||||||
|
if (highNibbleOrd < 0) {
|
||||||
|
// Not a hex digit.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int lowNibbleOrd = HEX_DIGITS_STR.indexOf(s.charAt(i + 1));
|
||||||
|
if (lowNibbleOrd < 0) {
|
||||||
|
// Not a hex digit
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
rval = concat(rval, (byte) (highNibbleOrd * 16 + lowNibbleOrd));
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public static byte[] fromByteList(List<Byte> byteArray) {
|
||||||
|
// byte[] rval = new byte[byteArray.size()];
|
||||||
|
// for (int i = 0; i < byteArray.size(); i++) {
|
||||||
|
// rval[i] = byteArray.get(i);
|
||||||
|
// }
|
||||||
|
// return rval;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public static List<Byte> toByteList(byte[] data) {
|
||||||
|
// ArrayList<Byte> rval = new ArrayList<>(data.length);
|
||||||
|
// for (int i = 0; i < data.length; i++) {
|
||||||
|
// rval.add(i, new Byte(data[i]));
|
||||||
|
// }
|
||||||
|
// return rval;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static List<Byte> getListFromByteArray(byte[] array) {
|
||||||
|
List<Byte> listOut = new ArrayList<Byte>();
|
||||||
|
|
||||||
|
for (byte val : array) {
|
||||||
|
listOut.add(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] getByteArrayFromList(List<Byte> list) {
|
||||||
|
byte[] out = new byte[list.size()];
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
out[i] = list.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// compares byte strings like strcmp
|
||||||
|
public static int compare(byte[] s1, byte[] s2) {
|
||||||
|
int i;
|
||||||
|
int len1 = s1.length;
|
||||||
|
int len2 = s2.length;
|
||||||
|
if (len1 > len2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (len2 > len1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int acc = 0;
|
||||||
|
for (i = 0; i < len1; i++) {
|
||||||
|
acc += s1[i];
|
||||||
|
acc -= s2[i];
|
||||||
|
if (acc != 0) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts 4 (or less) ints into int. (Shorts are objects, so you can send null if you have less parameters)
|
||||||
|
*
|
||||||
|
* @param b1 short 1
|
||||||
|
* @param b2 short 2
|
||||||
|
* @param b3 short 3
|
||||||
|
* @param b4 short 4
|
||||||
|
* @param flag Conversion Flag (Big Endian, Little endian)
|
||||||
|
* @return int value
|
||||||
|
*/
|
||||||
|
public static int toInt(Integer b1, Integer b2, Integer b3, Integer b4, BitConversion flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case LITTLE_ENDIAN: {
|
||||||
|
if (b4 != null) {
|
||||||
|
return (b4 & 0xff) << 24 | (b3 & 0xff) << 16 | (b2 & 0xff) << 8 | b1 & 0xff;
|
||||||
|
} else if (b3 != null) {
|
||||||
|
return (b3 & 0xff) << 16 | (b2 & 0xff) << 8 | b1 & 0xff;
|
||||||
|
} else if (b2 != null) {
|
||||||
|
return (b2 & 0xff) << 8 | b1 & 0xff;
|
||||||
|
} else {
|
||||||
|
return b1 & 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
case BIG_ENDIAN: {
|
||||||
|
if (b4 != null) {
|
||||||
|
return (b1 & 0xff) << 24 | (b2 & 0xff) << 16 | (b3 & 0xff) << 8 | b4 & 0xff;
|
||||||
|
} else if (b3 != null) {
|
||||||
|
return (b1 & 0xff) << 16 | (b2 & 0xff) << 8 | b3 & 0xff;
|
||||||
|
} else if (b2 != null) {
|
||||||
|
return (b1 & 0xff) << 8 | b2 & 0xff;
|
||||||
|
} else {
|
||||||
|
return b1 & 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int toInt(int b1, int b2) {
|
||||||
|
return toInt(b1, b2, null, null, BitConversion.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int toInt(int b1, int b2, int b3) {
|
||||||
|
return toInt(b1, b2, b3, null, BitConversion.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int toInt(int b1, int b2, BitConversion flag) {
|
||||||
|
return toInt(b1, b2, null, null, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int makeUnsignedShort(int i, int j) {
|
||||||
|
int k = (i & 0xff) << 8 | j & 0xff;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the correct hex value.
|
||||||
|
*
|
||||||
|
* @param inp the inp
|
||||||
|
* @return the correct hex value
|
||||||
|
*/
|
||||||
|
public static String getCorrectHexValue(int inp) {
|
||||||
|
String hx = Integer.toHexString((char) inp);
|
||||||
|
|
||||||
|
if (hx.length() == 0)
|
||||||
|
return "00";
|
||||||
|
else if (hx.length() == 1)
|
||||||
|
return "0" + hx;
|
||||||
|
else if (hx.length() == 2)
|
||||||
|
return hx;
|
||||||
|
else if (hx.length() == 4)
|
||||||
|
return hx.substring(2);
|
||||||
|
else {
|
||||||
|
System.out.println("Hex Error: " + inp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHex(byte abyte0[]) {
|
||||||
|
return abyte0 != null ? getHex(abyte0, abyte0.length) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getString(short abyte0[]) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (short i : abyte0) {
|
||||||
|
sb.append(i);
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHex(List<Byte> list) {
|
||||||
|
|
||||||
|
byte[] abyte0 = getByteArrayFromList(list);
|
||||||
|
|
||||||
|
return abyte0 != null ? getHex(abyte0, abyte0.length) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHex(byte abyte0[], int i) {
|
||||||
|
StringBuffer stringbuffer = new StringBuffer();
|
||||||
|
if (abyte0 != null) {
|
||||||
|
i = Math.min(i, abyte0.length);
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
stringbuffer.append(shortHexString(abyte0[j]));
|
||||||
|
if (j < i - 1) {
|
||||||
|
stringbuffer.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return new String(stringbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHex(byte byte0) {
|
||||||
|
String s = byte0 != -1 ? "0x" : "";
|
||||||
|
return s + getHexCompact(byte0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getHexCompact(byte byte0) {
|
||||||
|
int i = byte0 != -1 ? convertUnsignedByteToInt(byte0) : (int) byte0;
|
||||||
|
return getHexCompact(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int convertUnsignedByteToInt(byte data) {
|
||||||
|
return data & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public String getHexCompact(int i) {
|
||||||
|
// long l = i != -1 ? convertUnsignedIntToLong(i) : i;
|
||||||
|
// return getHexCompact(l);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static String getHexCompact(int l) {
|
||||||
|
String s = Long.toHexString(l).toUpperCase();
|
||||||
|
String s1 = isOdd(s.length()) ? "0" : "";
|
||||||
|
return l != -1L ? s1 + s : "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isEven(int i) {
|
||||||
|
return i % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isOdd(int i) {
|
||||||
|
return !isEven(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BitConversion {
|
||||||
|
LITTLE_ENDIAN, // 20 0 0 0 = reverse
|
||||||
|
BIG_ENDIAN // 0 0 0 20 = normal - java
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getCompactString(byte[] data) {
|
||||||
|
if (data == null)
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
String vval2 = ByteUtil.getHex(data);
|
||||||
|
vval2 = vval2.replace(" 0x", "");
|
||||||
|
vval2 = vval2.replace("0x", "");
|
||||||
|
return vval2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 000300050100C800A0
|
||||||
|
public static byte[] createByteArrayFromCompactString(String dataFull) {
|
||||||
|
return createByteArrayFromCompactString(dataFull, 0, dataFull.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 00 03 00 05 01 00 C8 00 A0
|
||||||
|
public static byte[] createByteArrayFromString(String dataFull) {
|
||||||
|
|
||||||
|
String data = dataFull.replace(" ", "");
|
||||||
|
|
||||||
|
return createByteArrayFromCompactString(data, 0, data.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] createByteArrayFromHexString(String dataFull) {
|
||||||
|
|
||||||
|
String data = dataFull.replace(" 0x", "");
|
||||||
|
data = data.replace("0x", "");
|
||||||
|
|
||||||
|
return createByteArrayFromCompactString(data, 0, data.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] createByteArrayFromCompactString(String dataFull, int startIndex) {
|
||||||
|
return createByteArrayFromCompactString(dataFull, startIndex, dataFull.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] createByteArrayFromCompactString(String dataFull, int startIndex, int length) {
|
||||||
|
|
||||||
|
String data = dataFull.substring(startIndex);
|
||||||
|
|
||||||
|
data = data.substring(0, length);
|
||||||
|
|
||||||
|
int len = data.length();
|
||||||
|
byte[] outArray = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
outArray[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4) + Character.digit(data.charAt(i + 1),
|
||||||
|
16));
|
||||||
|
}
|
||||||
|
|
||||||
|
return outArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 4/27/15.
|
||||||
|
*/
|
||||||
|
public class CRC {
|
||||||
|
|
||||||
|
static final int[] crc8lookup = new int[] {
|
||||||
|
0, 155, 173, 54, 193, 90, 108, 247, 25,
|
||||||
|
130,
|
||||||
|
180,
|
||||||
|
47,
|
||||||
|
216,
|
||||||
|
67,
|
||||||
|
117,
|
||||||
|
238,
|
||||||
|
50,
|
||||||
|
169, //
|
||||||
|
159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201,
|
||||||
|
82,
|
||||||
|
165,
|
||||||
|
62,
|
||||||
|
8,
|
||||||
|
147,
|
||||||
|
125,
|
||||||
|
230,
|
||||||
|
208,
|
||||||
|
75, //
|
||||||
|
188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21,
|
||||||
|
35,
|
||||||
|
184,
|
||||||
|
200,
|
||||||
|
83,
|
||||||
|
101,
|
||||||
|
254,
|
||||||
|
9,
|
||||||
|
146, //
|
||||||
|
164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227,
|
||||||
|
120,
|
||||||
|
78,
|
||||||
|
213,
|
||||||
|
34,
|
||||||
|
185,
|
||||||
|
143,
|
||||||
|
20, //
|
||||||
|
172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95,
|
||||||
|
196,
|
||||||
|
242,
|
||||||
|
105,
|
||||||
|
135,
|
||||||
|
28,
|
||||||
|
42, //
|
||||||
|
177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229,
|
||||||
|
57,
|
||||||
|
162,
|
||||||
|
148,
|
||||||
|
15,
|
||||||
|
248, //
|
||||||
|
99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64,
|
||||||
|
183,
|
||||||
|
44,
|
||||||
|
26,
|
||||||
|
129, //
|
||||||
|
93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52,
|
||||||
|
218,
|
||||||
|
65,
|
||||||
|
119, //
|
||||||
|
236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10,
|
||||||
|
145, 102,
|
||||||
|
253, //
|
||||||
|
203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214,
|
||||||
|
224, 123 };
|
||||||
|
|
||||||
|
static final int[] crc16lookup = new int[] {
|
||||||
|
0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d,
|
||||||
|
0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a,
|
||||||
|
0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5,
|
||||||
|
0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1,
|
||||||
|
0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d,
|
||||||
|
0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea,
|
||||||
|
0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145,
|
||||||
|
0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162,
|
||||||
|
0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e,
|
||||||
|
0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a,
|
||||||
|
0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356,
|
||||||
|
0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1,
|
||||||
|
0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd,
|
||||||
|
0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a,
|
||||||
|
0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6,
|
||||||
|
0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1,
|
||||||
|
0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e,
|
||||||
|
0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219,
|
||||||
|
0x0208, 0x820d, 0x8207, 0x0202 };
|
||||||
|
|
||||||
|
|
||||||
|
public static byte crc8(byte[] data, int len) {
|
||||||
|
byte result = 0;
|
||||||
|
if (data == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (len > data.length) {
|
||||||
|
len = data.length;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int tmp = result;
|
||||||
|
int tmp2 = tmp ^ data[i];
|
||||||
|
int tmp3 = tmp2 & 0xFF;
|
||||||
|
int idx = tmp3;
|
||||||
|
result = (byte)crc8lookup[idx];
|
||||||
|
// log(String.format("iter=%d,tmp=0x%02x, tmp2=0x%02x, tmp3=0x%02x, lookup=0x%02x",i,tmp,tmp2,tmp3,result));
|
||||||
|
}
|
||||||
|
// orig python:
|
||||||
|
// result = klass.lookup[ ( result ^ block[ i ] & 0xFF ) ]
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte crc8(byte[] data) {
|
||||||
|
return crc8(data, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static byte[] calculate16CCITT(byte[] data) {
|
||||||
|
int crc = 0xFFFF;
|
||||||
|
int polynomial = 0x1021;
|
||||||
|
if (data != null) {
|
||||||
|
if (data.length > 0) {
|
||||||
|
for (int j = 0; j < data.length; j++) {
|
||||||
|
byte b = data[j];
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
boolean bit = ((b >> (7 - i) & 1) == 1);
|
||||||
|
boolean c15 = ((crc >> 15 & 1) == 1);
|
||||||
|
crc <<= 1;
|
||||||
|
if (c15 ^ bit)
|
||||||
|
crc ^= polynomial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crc &= 0xffff;
|
||||||
|
return new byte[] { (byte)((crc & 0xFF00) >> 8), (byte)(crc & 0xFF) };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int crc16(byte[] bytes) {
|
||||||
|
int crc = 0x0000;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
crc = (crc >>> 8) ^ crc16lookup[(crc ^ b) & 0xff];
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.common.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 10/25/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime;
|
||||||
|
import org.joda.time.Minutes;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is simple version of ATechDate, limited only to one format (yyyymmddHHMIss)
|
||||||
|
*/
|
||||||
|
public class DateTimeUtil {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTime is packed as long: yyyymmddHHMMss
|
||||||
|
*
|
||||||
|
* @param atechDateTime
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static LocalDateTime toLocalDateTime(long atechDateTime) {
|
||||||
|
int year = (int) (atechDateTime / 10000000000L);
|
||||||
|
atechDateTime -= year * 10000000000L;
|
||||||
|
|
||||||
|
int month = (int) (atechDateTime / 100000000L);
|
||||||
|
atechDateTime -= month * 100000000L;
|
||||||
|
|
||||||
|
int dayOfMonth = (int) (atechDateTime / 1000000L);
|
||||||
|
atechDateTime -= dayOfMonth * 1000000L;
|
||||||
|
|
||||||
|
int hourOfDay = (int) (atechDateTime / 10000L);
|
||||||
|
atechDateTime -= hourOfDay * 10000L;
|
||||||
|
|
||||||
|
int minute = (int) (atechDateTime / 100L);
|
||||||
|
atechDateTime -= minute * 100L;
|
||||||
|
|
||||||
|
int second = (int) atechDateTime;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new LocalDateTime(year, month, dayOfMonth, hourOfDay, minute, second);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (L.isEnabled(L.PUMPCOMM))
|
||||||
|
LOG.error("Error creating LocalDateTime from values [atechDateTime={}, year={}, month={}, day={}, hour={}, minute={}, second={}]. Exception: {}", atechDateTime, year, month, dayOfMonth, hourOfDay, minute, second, ex.getMessage());
|
||||||
|
//return null;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTime is packed as long: yyyymmddHHMMss
|
||||||
|
*
|
||||||
|
* @param atechDateTime
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static GregorianCalendar toGregorianCalendar(long atechDateTime) {
|
||||||
|
int year = (int) (atechDateTime / 10000000000L);
|
||||||
|
atechDateTime -= year * 10000000000L;
|
||||||
|
|
||||||
|
int month = (int) (atechDateTime / 100000000L);
|
||||||
|
atechDateTime -= month * 100000000L;
|
||||||
|
|
||||||
|
int dayOfMonth = (int) (atechDateTime / 1000000L);
|
||||||
|
atechDateTime -= dayOfMonth * 1000000L;
|
||||||
|
|
||||||
|
int hourOfDay = (int) (atechDateTime / 10000L);
|
||||||
|
atechDateTime -= hourOfDay * 10000L;
|
||||||
|
|
||||||
|
int minute = (int) (atechDateTime / 100L);
|
||||||
|
atechDateTime -= minute * 100L;
|
||||||
|
|
||||||
|
int second = (int) atechDateTime;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new GregorianCalendar(year, month - 1, dayOfMonth, hourOfDay, minute, second);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (L.isEnabled(L.PUMPCOMM))
|
||||||
|
LOG.error("DateTimeUtil", String.format("Error creating GregorianCalendar from values [atechDateTime=%d, year=%d, month=%d, day=%d, hour=%d, minute=%d, second=%d]", atechDateTime, year, month, dayOfMonth, hourOfDay, minute, second));
|
||||||
|
//return null;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static long toATechDate(LocalDateTime ldt) {
|
||||||
|
long atechDateTime = 0L;
|
||||||
|
|
||||||
|
atechDateTime += ldt.getYear() * 10000000000L;
|
||||||
|
atechDateTime += ldt.getMonthOfYear() * 100000000L;
|
||||||
|
atechDateTime += ldt.getDayOfMonth() * 1000000L;
|
||||||
|
atechDateTime += ldt.getHourOfDay() * 10000L;
|
||||||
|
atechDateTime += ldt.getMinuteOfHour() * 100L;
|
||||||
|
atechDateTime += ldt.getSecondOfMinute();
|
||||||
|
|
||||||
|
return atechDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static long toATechDate(GregorianCalendar gc) {
|
||||||
|
long atechDateTime = 0L;
|
||||||
|
|
||||||
|
atechDateTime += gc.get(Calendar.YEAR) * 10000000000L;
|
||||||
|
atechDateTime += (gc.get(Calendar.MONTH) + 1) * 100000000L;
|
||||||
|
atechDateTime += gc.get(Calendar.DAY_OF_MONTH) * 1000000L;
|
||||||
|
atechDateTime += gc.get(Calendar.HOUR_OF_DAY) * 10000L;
|
||||||
|
atechDateTime += gc.get(Calendar.MINUTE) * 100L;
|
||||||
|
atechDateTime += gc.get(Calendar.SECOND);
|
||||||
|
|
||||||
|
return atechDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSameDay(LocalDateTime ldt1, LocalDateTime ldt2) {
|
||||||
|
|
||||||
|
return (ldt1.getYear() == ldt2.getYear() && //
|
||||||
|
ldt1.getMonthOfYear() == ldt2.getMonthOfYear() && //
|
||||||
|
ldt1.getDayOfMonth() == ldt2.getDayOfMonth());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSameDay(long ldt1, long ldt2) {
|
||||||
|
|
||||||
|
long day1 = ldt1 / 10000L;
|
||||||
|
long day2 = ldt2 / 10000L;
|
||||||
|
|
||||||
|
return day1 == day2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static long toATechDate(int year, int month, int dayOfMonth, int hour, int minutes, int seconds) {
|
||||||
|
|
||||||
|
long atechDateTime = 0L;
|
||||||
|
|
||||||
|
atechDateTime += year * 10000000000L;
|
||||||
|
atechDateTime += month * 100000000L;
|
||||||
|
atechDateTime += dayOfMonth * 1000000L;
|
||||||
|
atechDateTime += hour * 10000L;
|
||||||
|
atechDateTime += minutes * 100L;
|
||||||
|
atechDateTime += seconds;
|
||||||
|
|
||||||
|
return atechDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public static long toATechDate(Date date) {
|
||||||
|
//
|
||||||
|
// long atechDateTime = 0L;
|
||||||
|
//
|
||||||
|
// atechDateTime += (date.getYear() + 1900) * 10000000000L;
|
||||||
|
// atechDateTime += (date.getMonth() + 1) * 100000000L;
|
||||||
|
// atechDateTime += date.getDate() * 1000000L;
|
||||||
|
// atechDateTime += date.getHours() * 10000L;
|
||||||
|
// atechDateTime += date.getMinutes() * 100L;
|
||||||
|
// atechDateTime += date.getSeconds();
|
||||||
|
//
|
||||||
|
// return atechDateTime;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
public static String toString(long atechDateTime) {
|
||||||
|
int year = (int) (atechDateTime / 10000000000L);
|
||||||
|
atechDateTime -= year * 10000000000L;
|
||||||
|
|
||||||
|
int month = (int) (atechDateTime / 100000000L);
|
||||||
|
atechDateTime -= month * 100000000L;
|
||||||
|
|
||||||
|
int dayOfMonth = (int) (atechDateTime / 1000000L);
|
||||||
|
atechDateTime -= dayOfMonth * 1000000L;
|
||||||
|
|
||||||
|
int hourOfDay = (int) (atechDateTime / 10000L);
|
||||||
|
atechDateTime -= hourOfDay * 10000L;
|
||||||
|
|
||||||
|
int minute = (int) (atechDateTime / 100L);
|
||||||
|
atechDateTime -= minute * 100L;
|
||||||
|
|
||||||
|
int second = (int) atechDateTime;
|
||||||
|
|
||||||
|
return getZeroPrefixed(dayOfMonth) + "." + getZeroPrefixed(month) + "." + year + " " + //
|
||||||
|
getZeroPrefixed(hourOfDay) + ":" + getZeroPrefixed(minute) + ":" + getZeroPrefixed(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String toString(GregorianCalendar gc) {
|
||||||
|
|
||||||
|
return getZeroPrefixed(gc.get(Calendar.DAY_OF_MONTH)) + "." + getZeroPrefixed(gc.get(Calendar.MONTH) + 1) + "."
|
||||||
|
+ gc.get(Calendar.YEAR) + " "
|
||||||
|
+ //
|
||||||
|
getZeroPrefixed(gc.get(Calendar.HOUR_OF_DAY)) + ":" + getZeroPrefixed(gc.get(Calendar.MINUTE)) + ":"
|
||||||
|
+ getZeroPrefixed(gc.get(Calendar.SECOND));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String toStringFromTimeInMillis(long timeInMillis) {
|
||||||
|
|
||||||
|
GregorianCalendar gc = new GregorianCalendar();
|
||||||
|
gc.setTimeInMillis(timeInMillis);
|
||||||
|
|
||||||
|
return toString(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String getZeroPrefixed(int number) {
|
||||||
|
return (number < 10) ? "0" + number : "" + number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int getYear(Long atechDateTime) {
|
||||||
|
|
||||||
|
if (atechDateTime == null || atechDateTime == 0) {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int year = (int) (atechDateTime / 10000000000L);
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSameDayATDAndMillis(long atechDateTime, long timeInMillis) {
|
||||||
|
|
||||||
|
GregorianCalendar dt = new GregorianCalendar();
|
||||||
|
dt.setTimeInMillis(timeInMillis);
|
||||||
|
|
||||||
|
long entryDate = toATechDate(dt);
|
||||||
|
|
||||||
|
return (isSameDay(atechDateTime, entryDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static long toMillisFromATD(long atechDateTime) {
|
||||||
|
|
||||||
|
GregorianCalendar gc = toGregorianCalendar(atechDateTime);
|
||||||
|
|
||||||
|
return gc.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int getATechDateDiferenceAsMinutes(Long date1, Long date2) {
|
||||||
|
|
||||||
|
Minutes minutes = Minutes.minutesBetween(toLocalDateTime(date1), toLocalDateTime(date2));
|
||||||
|
|
||||||
|
return minutes.getMinutes();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue