Merge pull request #1868 from andyrozman/medtronic_andy_merge

Medtronic driver merge
This commit is contained in:
Milos Kozak 2019-07-25 16:15:17 +02:00 committed by GitHub
commit 0b28c92c2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
210 changed files with 24901 additions and 63 deletions

View file

@ -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

View file

@ -282,9 +282,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}"

View file

@ -284,6 +284,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>

View file

@ -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;
@ -63,12 +68,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;
@ -86,6 +94,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;
@ -113,6 +122,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;
@ -154,6 +165,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<>();
@ -175,6 +187,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());
@ -254,6 +267,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() {
@ -439,5 +456,14 @@ public class MainApp extends Application {
sDatabaseHelper.close(); sDatabaseHelper.close();
sDatabaseHelper = null; sDatabaseHelper = null;
} }
if (btReceiver != null) {
unregisterReceiver(btReceiver);
}
if (timeDateOrTZChangeReceiver!=null) {
unregisterReceiver(timeDateOrTZChangeReceiver);
}
} }
} }

View file

@ -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;
@ -170,6 +171,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)

View file

@ -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) {

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.db;
public interface DbObjectBase {
long getDate();
long getPumpId();
}

View file

@ -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 +
']';
}
} }

View file

@ -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;
}
} }

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.events;
public class EventCustomActionsChanged extends Event {
}

View file

@ -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();
} }

View file

@ -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)

View file

@ -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();
} }

View file

@ -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;
}
} }

View file

@ -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;
@ -75,6 +74,7 @@ public class Notification {
public static final int DST_IN_24H = 50; public static final int DST_IN_24H = 50;
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 OVER_24H_TIME_CHANGE_REQUESTED = 53;
public int id; public int id;
@ -85,6 +85,7 @@ public class Notification {
public NSAlarm nsAlarm = null; public NSAlarm nsAlarm = null;
public Integer soundId = null; public Integer soundId = null;
public Notification() { public Notification() {
} }

View file

@ -1408,4 +1408,10 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
return false; return false;
} }
@Override
public void timeDateOrTimeZoneChanged() {
}
} }

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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, //

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -102,7 +102,7 @@ public enum PumpType {
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),
@ -111,7 +111,7 @@ public enum PumpType {
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

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.common.dialog;
/**
* Created by andy on 5/19/18.
*/
public interface RefreshableInterface {
void refreshData();
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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";
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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]);
}
}

View file

@ -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);
}
}

View file

@ -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;
});
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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)
;
}

View file

@ -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()));
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
// }
}

View file

@ -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);
}
}

View file

@ -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]);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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)
;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}
}

View file

@ -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 + "]";
}
}

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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", "");
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
* }
*/
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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