Merge pull request #1868 from andyrozman/medtronic_andy_merge
Medtronic driver merge
This commit is contained in:
commit
0b28c92c2d
|
@ -14,7 +14,7 @@ android:
|
|||
- extra-google-google_play_services
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-28"
|
||||
#- yes | sdkmanager "platforms;android-28"
|
||||
|
||||
script:
|
||||
# Unit Test
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
dev: [![codecov](https://codecov.io/gh/MilosKozak/AndroidAPS/branch/dev/graph/badge.svg)](https://codecov.io/gh/MilosKozak/AndroidAPS)
|
||||
|
||||
|
||||
[![Donate via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y4LHGJJESAVB8)
|
||||
[![Donate via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y4LHGJJESAVB8)
|
|
@ -282,9 +282,17 @@ dependencies {
|
|||
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
|
||||
testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}"
|
||||
testImplementation "joda-time:joda-time:2.9.9"
|
||||
testImplementation "com.google.truth:truth:0.39"
|
||||
testImplementation 'org.robolectric:robolectric:4.2.1'
|
||||
testImplementation("com.google.truth:truth:0.39") {
|
||||
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.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 "com.google.dexmaker:dexmaker:${dexmakerVersion}"
|
||||
|
|
|
@ -284,6 +284,23 @@
|
|||
android:theme="@style/AppTheme" />
|
||||
<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"/>
|
||||
|
||||
</application>
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package info.nightscout.androidaps;
|
||||
|
||||
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.res.Resources;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
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.simple.SimpleProfilePlugin;
|
||||
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.danaRKorean.DanaRKoreanPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
|
||||
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
|
||||
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.sensitivity.SensitivityAAPSPlugin;
|
||||
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.KeepAliveReceiver;
|
||||
import info.nightscout.androidaps.receivers.NSAlarmReceiver;
|
||||
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver;
|
||||
import info.nightscout.androidaps.services.Intents;
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
@ -113,6 +122,8 @@ public class MainApp extends Application {
|
|||
private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver();
|
||||
private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver();
|
||||
private LocalBroadcastManager lbm;
|
||||
BroadcastReceiver btReceiver;
|
||||
TimeDateOrTZChangeReceiver timeDateOrTZChangeReceiver;
|
||||
|
||||
public static boolean devBranch;
|
||||
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
|
||||
triggerCheckVersion();
|
||||
//setBTReceiver();
|
||||
|
||||
if (pluginsList == null) {
|
||||
pluginsList = new ArrayList<>();
|
||||
|
@ -175,6 +187,7 @@ public class MainApp extends Application {
|
|||
if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin());
|
||||
pluginsList.add(CareportalPlugin.getPlugin());
|
||||
if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin());
|
||||
if (Config.PUMPDRIVERS && engineeringMode) pluginsList.add(MedtronicPumpPlugin.getPlugin());
|
||||
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
|
||||
pluginsList.add(VirtualPumpPlugin.getPlugin());
|
||||
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());
|
||||
|
@ -254,6 +267,10 @@ public class MainApp extends Application {
|
|||
|
||||
//register dbaccess
|
||||
lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE));
|
||||
|
||||
this.timeDateOrTZChangeReceiver = new TimeDateOrTZChangeReceiver();
|
||||
this.timeDateOrTZChangeReceiver.registerBroadcasts(this);
|
||||
|
||||
}
|
||||
|
||||
private void startKeepAliveService() {
|
||||
|
@ -439,5 +456,14 @@ public class MainApp extends Application {
|
|||
sDatabaseHelper.close();
|
||||
sDatabaseHelper = null;
|
||||
}
|
||||
|
||||
if (btReceiver != null) {
|
||||
unregisterReceiver(btReceiver);
|
||||
}
|
||||
|
||||
if (timeDateOrTZChangeReceiver!=null) {
|
||||
unregisterReceiver(timeDateOrTZChangeReceiver);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.danaRv2.DanaRv2Plugin;
|
||||
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.sensitivity.SensitivityAAPSPlugin;
|
||||
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
|
||||
|
@ -170,6 +171,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
|
|||
addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP);
|
||||
addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP);
|
||||
addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP);
|
||||
addPreferencesFromResourceIfEnabled(MedtronicPumpPlugin.getPlugin(), PluginType.PUMP);
|
||||
|
||||
if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
||||
|| DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|
||||
|
|
|
@ -21,6 +21,8 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
@ -536,6 +538,24 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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 -------------------
|
||||
|
||||
|
@ -871,6 +891,31 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
||||
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);
|
||||
if (L.isEnabled(L.DATABASE))
|
||||
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
|
||||
|
@ -1111,6 +1156,29 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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 ---------------
|
||||
|
||||
public boolean createOrUpdate(ExtendedBolus extendedBolus) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package info.nightscout.androidaps.db;
|
||||
|
||||
public interface DbObjectBase {
|
||||
|
||||
long getDate();
|
||||
|
||||
long getPumpId();
|
||||
|
||||
}
|
|
@ -6,9 +6,8 @@ import com.j256.ormlite.table.DatabaseTable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
||||
|
||||
/**
|
||||
* Created by mike on 20.09.2017.
|
||||
|
@ -45,4 +44,16 @@ public class TDD {
|
|||
this.basal = basal;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TDD [" +
|
||||
"date=" + date +
|
||||
"date(str)=" + DateTimeUtil.toStringFromTimeInMillis(date) +
|
||||
", bolus=" + bolus +
|
||||
", basal=" + basal +
|
||||
", total=" + total +
|
||||
']';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import info.nightscout.androidaps.utils.SP;
|
|||
*/
|
||||
|
||||
@DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS)
|
||||
public class TemporaryBasal implements Interval {
|
||||
public class TemporaryBasal implements Interval, DbObjectBase {
|
||||
private static Logger log = LoggerFactory.getLogger(L.DATABASE);
|
||||
|
||||
@DatabaseField(id = true)
|
||||
|
@ -156,6 +156,14 @@ public class TemporaryBasal implements Interval {
|
|||
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 ---------
|
||||
|
||||
Long cuttedEnd = null;
|
||||
|
@ -416,4 +424,13 @@ public class TemporaryBasal implements Interval {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPumpId() {
|
||||
return this.pumpId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package info.nightscout.androidaps.events;
|
||||
|
||||
public class EventCustomActionsChanged extends Event {
|
||||
}
|
|
@ -75,4 +75,10 @@ public interface PumpInterface {
|
|||
|
||||
void executeCustomAction(CustomActionType customActionType);
|
||||
|
||||
/**
|
||||
* This method will be called when time or Timezone changes, and pump driver can then do a specific action (for
|
||||
* example update clock on pump).
|
||||
*/
|
||||
void timeDateOrTimeZoneChanged();
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ public interface TreatmentsInterface {
|
|||
|
||||
List<Treatment> getTreatmentsFromHistory();
|
||||
List<Treatment> getTreatments5MinBackFromHistory(long time);
|
||||
List<Treatment> getTreatmentsFromHistoryAfterTimestamp(long timestamp);
|
||||
long getLastBolusTime();
|
||||
|
||||
// real basals (not faked by extended bolus)
|
||||
|
@ -64,4 +65,4 @@ public interface TreatmentsInterface {
|
|||
|
||||
long oldestDataAvailable();
|
||||
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import info.nightscout.androidaps.activities.HistoryBrowseActivity;
|
|||
import info.nightscout.androidaps.activities.TDDStatsActivity;
|
||||
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||
import info.nightscout.androidaps.events.EventCustomActionsChanged;
|
||||
import info.nightscout.androidaps.events.EventExtendedBolusChange;
|
||||
import info.nightscout.androidaps.events.EventInitializationChanged;
|
||||
import info.nightscout.androidaps.events.EventRefreshOverview;
|
||||
|
@ -133,6 +134,11 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
|||
updateGUI();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventCustomActionsChanged ev) {
|
||||
updateGUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateGUI() {
|
||||
Activity activity = getActivity();
|
||||
|
@ -247,6 +253,9 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
|||
|
||||
for (CustomAction customAction : customActions) {
|
||||
|
||||
if (!customAction.isEnabled())
|
||||
continue;
|
||||
|
||||
SingleClickButton btn = new SingleClickButton(getContext(), null, android.R.attr.buttonStyle);
|
||||
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.pumpCustomButtons.add(btn);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -283,7 +290,6 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
|||
}
|
||||
|
||||
pumpCustomButtons.clear();
|
||||
pumpCustomActions.clear();
|
||||
}
|
||||
|
||||
|
||||
|
@ -335,4 +341,4 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,31 +12,36 @@ public class CustomAction {
|
|||
private String iconName;
|
||||
private CustomActionType customActionType;
|
||||
private int iconResourceId;
|
||||
private boolean enabled = true;
|
||||
|
||||
|
||||
public CustomAction(int nameResourceId, CustomActionType actionType) {
|
||||
this.name = nameResourceId;
|
||||
this.customActionType = actionType;
|
||||
this.iconResourceId = R.drawable.icon_actions_profileswitch;
|
||||
this(nameResourceId, actionType, R.drawable.icon_actions_profileswitch, true);
|
||||
}
|
||||
|
||||
|
||||
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.customActionType = actionType;
|
||||
this.iconResourceId = iconResourceId;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
public int getName() {
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public CustomActionType getCustomActionType() {
|
||||
|
||||
return customActionType;
|
||||
}
|
||||
|
||||
|
@ -44,4 +49,15 @@ public class CustomAction {
|
|||
public int getIconResourceId() {
|
||||
return iconResourceId;
|
||||
}
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1567,4 +1567,4 @@ public class OverviewFragment extends Fragment implements View.OnClickListener,
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package info.nightscout.androidaps.plugins.general.overview.notifications;
|
||||
|
||||
import java.util.Date;
|
||||
|
@ -75,6 +74,7 @@ public class Notification {
|
|||
public static final int DST_IN_24H = 50;
|
||||
public static final int DISKFULL = 51;
|
||||
public static final int OLDVERSION = 52;
|
||||
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 53;
|
||||
|
||||
|
||||
public int id;
|
||||
|
@ -85,6 +85,7 @@ public class Notification {
|
|||
|
||||
public NSAlarm nsAlarm = null;
|
||||
public Integer soundId = null;
|
||||
|
||||
public Notification() {
|
||||
}
|
||||
|
||||
|
@ -198,10 +199,10 @@ public class Notification {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAlarmForStaleData(){
|
||||
public static boolean isAlarmForStaleData() {
|
||||
long snoozedTo = SP.getLong("snoozedTo", 0L);
|
||||
if(snoozedTo != 0L){
|
||||
if(System.currentTimeMillis() < SP.getLong("snoozedTo", 0L)) {
|
||||
if (snoozedTo != 0L) {
|
||||
if (System.currentTimeMillis() < SP.getLong("snoozedTo", 0L)) {
|
||||
//log.debug("Alarm is snoozed for next "+(SP.getLong("snoozedTo", 0L)-System.currentTimeMillis())/1000+" seconds");
|
||||
return false;
|
||||
}
|
||||
|
@ -217,12 +218,12 @@ public class Notification {
|
|||
//log.debug("bgReadingAgoMin value is:"+bgReadingAgoMin);
|
||||
//log.debug("Stale alarm snoozed to: "+(System.currentTimeMillis() - snoozedTo)/60000L);
|
||||
Double threshold = NSSettingsStatus.getInstance().getThreshold("alarmTimeagoWarnMins");
|
||||
//log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts);
|
||||
// if no thresshold from Ns get it loccally
|
||||
if(threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue,15D);
|
||||
// No threshold of OpenAPS Alarm so using the one for BG
|
||||
// Added OpenAPSEnabledAlerts to alarm check
|
||||
if((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false))||(bgReadingAgoMin > threshold && openAPSEnabledAlerts)){
|
||||
//log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts);
|
||||
// if no thresshold from Ns get it loccally
|
||||
if (threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue, 15D);
|
||||
// No threshold of OpenAPS Alarm so using the one for BG
|
||||
// Added OpenAPSEnabledAlerts to alarm check
|
||||
if ((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false)) || (bgReadingAgoMin > threshold && openAPSEnabledAlerts)) {
|
||||
return true;
|
||||
}
|
||||
//snoozing for threshold
|
||||
|
|
|
@ -758,4 +758,4 @@ public class IobCobCalculatorPlugin extends PluginBase {
|
|||
if (upper >= arr.length) return arr[(int) lower];
|
||||
return arr[(int) lower] * (1 - weight) + arr[(int) upper] * weight;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1408,4 +1408,10 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void timeDateOrTimeZoneChanged() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,446 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import info.nightscout.androidaps.BuildConfig;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.data.DetailedBolusInfo;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.data.PumpEnactResult;
|
||||
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||
import info.nightscout.androidaps.events.EventAppExit;
|
||||
import info.nightscout.androidaps.interfaces.ConstraintsInterface;
|
||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||
import info.nightscout.androidaps.interfaces.PluginDescription;
|
||||
import info.nightscout.androidaps.interfaces.PumpDescription;
|
||||
import info.nightscout.androidaps.interfaces.PumpInterface;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress;
|
||||
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
|
||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
||||
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
||||
|
||||
/**
|
||||
* Created by andy on 23.04.18.
|
||||
*/
|
||||
|
||||
// When using this class, make sure that your first step is to create mConnection (see MedtronicPumpPlugin)
|
||||
|
||||
public abstract class PumpPluginAbstract extends PluginBase implements PumpInterface, ConstraintsInterface {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||
|
||||
protected static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult().success(false)
|
||||
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump_driver));
|
||||
protected static final PumpEnactResult OPERATION_NOT_YET_SUPPORTED = new PumpEnactResult().success(false)
|
||||
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump));
|
||||
|
||||
protected PumpDescription pumpDescription = new PumpDescription();
|
||||
protected PumpStatus pumpStatus;
|
||||
protected ServiceConnection serviceConnection = null;
|
||||
protected boolean serviceRunning = false;
|
||||
// protected boolean isInitialized = false;
|
||||
protected PumpDriverState pumpState = PumpDriverState.NotInitialized;
|
||||
protected boolean displayConnectionMessages = false;
|
||||
|
||||
|
||||
protected PumpPluginAbstract(PluginDescription pluginDescription, PumpType pumpType) {
|
||||
|
||||
super(pluginDescription);
|
||||
|
||||
pumpDescription.setPumpDescription(pumpType);
|
||||
|
||||
initPumpStatusData();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public abstract void initPumpStatusData();
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Context context = MainApp.instance().getApplicationContext();
|
||||
Intent intent = new Intent(context, getServiceClass());
|
||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
serviceRunning = true;
|
||||
|
||||
MainApp.bus().register(this);
|
||||
onStartCustomActions();
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Context context = MainApp.instance().getApplicationContext();
|
||||
context.unbindService(serviceConnection);
|
||||
|
||||
serviceRunning = false;
|
||||
|
||||
MainApp.bus().unregister(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If we need to run any custom actions in onStart (triggering events, etc)
|
||||
*/
|
||||
public abstract void onStartCustomActions();
|
||||
|
||||
|
||||
/**
|
||||
* Service class (same one you did serviceConnection for)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Class getServiceClass();
|
||||
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
@Subscribe
|
||||
public void onStatusEvent(final EventAppExit e) {
|
||||
MainApp.instance().getApplicationContext().unbindService(serviceConnection);
|
||||
}
|
||||
|
||||
|
||||
public PumpStatus getPumpStatusData() {
|
||||
return pumpStatus;
|
||||
}
|
||||
|
||||
|
||||
public boolean isInitialized() {
|
||||
return PumpDriverState.isInitialized(pumpState);
|
||||
}
|
||||
|
||||
|
||||
public boolean isSuspended() {
|
||||
return pumpState == PumpDriverState.Suspended;
|
||||
}
|
||||
|
||||
|
||||
public boolean isBusy() {
|
||||
return pumpState == PumpDriverState.Busy;
|
||||
}
|
||||
|
||||
|
||||
public boolean isConnected() {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("isConnected [PumpPluginAbstract].");
|
||||
return PumpDriverState.isConnected(pumpState);
|
||||
}
|
||||
|
||||
|
||||
public boolean isConnecting() {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("isConnecting [PumpPluginAbstract].");
|
||||
return pumpState == PumpDriverState.Connecting;
|
||||
}
|
||||
|
||||
|
||||
public void connect(String reason) {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("connect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
|
||||
}
|
||||
|
||||
|
||||
public void disconnect(String reason) {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("disconnect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
|
||||
}
|
||||
|
||||
|
||||
public void stopConnecting() {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("stopConnecting [PumpPluginAbstract] - default (empty) implementation.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isHandshakeInProgress() {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("isHandshakeInProgress [PumpPluginAbstract] - default (empty) implementation.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void finishHandshaking() {
|
||||
if (displayConnectionMessages && isLoggingEnabled())
|
||||
LOG.warn("finishHandshaking [PumpPluginAbstract] - default (empty) implementation.");
|
||||
}
|
||||
|
||||
|
||||
public void getPumpStatus() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("getPumpStatus [PumpPluginAbstract] - Not implemented.");
|
||||
}
|
||||
|
||||
|
||||
// Upload to pump new basal profile
|
||||
public PumpEnactResult setNewBasalProfile(Profile profile) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("setNewBasalProfile [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
public boolean isThisProfileSet(Profile profile) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("isThisProfileSet [PumpPluginAbstract] - Not implemented.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public long lastDataTime() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("lastDataTime [PumpPluginAbstract].");
|
||||
return pumpStatus.lastConnection;
|
||||
}
|
||||
|
||||
|
||||
public double getBaseBasalRate() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("getBaseBasalRate [PumpPluginAbstract] - Not implemented.");
|
||||
return 0.0d;
|
||||
} // base basal rate, not temp basal
|
||||
|
||||
|
||||
public void stopBolusDelivering() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("stopBolusDelivering [PumpPluginAbstract] - Not implemented.");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile,
|
||||
boolean enforceNew) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("setTempBasalAbsolute [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile,
|
||||
boolean enforceNew) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("setTempBasalPercent [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("setExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
// some pumps might set a very short temp close to 100% as cancelling a temp can be noisy
|
||||
// when the cancel request is requested by the user (forced), the pump should always do a real cancel
|
||||
|
||||
public PumpEnactResult cancelTempBasal(boolean enforceNew) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("cancelTempBasal [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
public PumpEnactResult cancelExtendedBolus() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("cancelExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
// Status to be passed to NS
|
||||
|
||||
// public JSONObject getJSONStatus(Profile profile, String profileName) {
|
||||
// return pumpDriver.getJSONStatus(profile, profileName);
|
||||
// }
|
||||
|
||||
public String deviceID() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("deviceID [PumpPluginAbstract] - Not implemented.");
|
||||
return "FakeDevice";
|
||||
}
|
||||
|
||||
|
||||
// Pump capabilities
|
||||
|
||||
public PumpDescription getPumpDescription() {
|
||||
return pumpDescription;
|
||||
}
|
||||
|
||||
|
||||
// Short info for SMS, Wear etc
|
||||
|
||||
public boolean isFakingTempsByExtendedBoluses() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PumpEnactResult loadTDDs() {
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("loadTDDs [PumpPluginAbstract] - Not implemented.");
|
||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JSONObject getJSONStatus(Profile profile, String profileName) {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if ((pumpStatus.lastConnection + 5 * 60 * 1000L) < System.currentTimeMillis()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject pump = new JSONObject();
|
||||
JSONObject battery = new JSONObject();
|
||||
JSONObject status = new JSONObject();
|
||||
JSONObject extended = new JSONObject();
|
||||
try {
|
||||
battery.put("percent", pumpStatus.batteryRemaining);
|
||||
status.put("status", pumpStatus.pumpStatusType != null ? pumpStatus.pumpStatusType.getStatus() : "normal");
|
||||
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
|
||||
try {
|
||||
extended.put("ActiveProfile", profileName);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());
|
||||
if (tb != null) {
|
||||
extended.put("TempBasalAbsoluteRate",
|
||||
tb.tempBasalConvertedToAbsolute(System.currentTimeMillis(), profile));
|
||||
extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date));
|
||||
extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes());
|
||||
}
|
||||
|
||||
ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis());
|
||||
if (eb != null) {
|
||||
extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate());
|
||||
extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date));
|
||||
extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes());
|
||||
}
|
||||
|
||||
status.put("timestamp", DateUtil.toISOString(new Date()));
|
||||
|
||||
pump.put("battery", battery);
|
||||
pump.put("status", status);
|
||||
pump.put("extended", extended);
|
||||
pump.put("reservoir", pumpStatus.reservoirRemainingUnits);
|
||||
pump.put("clock", DateUtil.toISOString(new Date()));
|
||||
} catch (JSONException e) {
|
||||
LOG.error("Unhandled exception", e);
|
||||
}
|
||||
return pump;
|
||||
}
|
||||
|
||||
|
||||
// FIXME i18n, null checks: iob, TDD
|
||||
@Override
|
||||
public String shortStatus(boolean veryShort) {
|
||||
String ret = "";
|
||||
if (pumpStatus.lastConnection != 0) {
|
||||
Long agoMsec = System.currentTimeMillis() - pumpStatus.lastConnection;
|
||||
int agoMin = (int)(agoMsec / 60d / 1000d);
|
||||
ret += "LastConn: " + agoMin + " min ago\n";
|
||||
}
|
||||
if (pumpStatus.lastBolusTime != null && pumpStatus.lastBolusTime.getTime() != 0) {
|
||||
ret += "LastBolus: " + DecimalFormatter.to2Decimal(pumpStatus.lastBolusAmount) + "U @" + //
|
||||
android.text.format.DateFormat.format("HH:mm", pumpStatus.lastBolusTime) + "\n";
|
||||
}
|
||||
TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin()
|
||||
.getRealTempBasalFromHistory(System.currentTimeMillis());
|
||||
if (activeTemp != null) {
|
||||
ret += "Temp: " + activeTemp.toStringFull() + "\n";
|
||||
}
|
||||
ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(
|
||||
System.currentTimeMillis());
|
||||
if (activeExtendedBolus != null) {
|
||||
ret += "Extended: " + activeExtendedBolus.toString() + "\n";
|
||||
}
|
||||
// if (!veryShort) {
|
||||
// ret += "TDD: " + DecimalFormatter.to0Decimal(pumpStatus.dailyTotalUnits) + " / "
|
||||
// + pumpStatus.maxDailyTotalUnits + " U\n";
|
||||
// }
|
||||
ret += "IOB: " + pumpStatus.iob + "U\n";
|
||||
ret += "Reserv: " + DecimalFormatter.to0Decimal(pumpStatus.reservoirRemainingUnits) + "U\n";
|
||||
ret += "Batt: " + pumpStatus.batteryRemaining + "\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
|
||||
|
||||
try {
|
||||
if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
|
||||
// neither carbs nor bolus requested
|
||||
if (isLoggingEnabled())
|
||||
LOG.error("deliverTreatment: Invalid input");
|
||||
return new PumpEnactResult().success(false).enacted(false).bolusDelivered(0d).carbsDelivered(0d)
|
||||
.comment(MainApp.gs(R.string.danar_invalidinput));
|
||||
} else if (detailedBolusInfo.insulin > 0) {
|
||||
// bolus needed, ask pump to deliver it
|
||||
return deliverBolus(detailedBolusInfo);
|
||||
} else {
|
||||
// no bolus required, carb only treatment
|
||||
TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true);
|
||||
|
||||
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
|
||||
bolusingEvent.t = new Treatment();
|
||||
bolusingEvent.t.isSMB = detailedBolusInfo.isSMB;
|
||||
bolusingEvent.percent = 100;
|
||||
MainApp.bus().post(bolusingEvent);
|
||||
|
||||
if (isLoggingEnabled())
|
||||
LOG.debug("deliverTreatment: Carb only treatment.");
|
||||
|
||||
return new PumpEnactResult().success(true).enacted(true).bolusDelivered(0d)
|
||||
.carbsDelivered(detailedBolusInfo.carbs).comment(MainApp.gs(R.string.virtualpump_resultok));
|
||||
}
|
||||
} finally {
|
||||
triggerUIChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean isLoggingEnabled() {
|
||||
return L.isEnabled(L.PUMP);
|
||||
}
|
||||
|
||||
|
||||
protected abstract PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo);
|
||||
|
||||
|
||||
protected abstract void triggerUIChange();
|
||||
|
||||
|
||||
public static PumpEnactResult getOperationNotSupportedWithCustomText(int resourceId) {
|
||||
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(resourceId));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
import info.nightscout.androidaps.data.ProfileStore;
|
||||
import info.nightscout.androidaps.interfaces.PumpDescription;
|
||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
||||
|
||||
/**
|
||||
* Created by andy on 4/28/18.
|
||||
*/
|
||||
|
||||
public abstract class PumpStatus {
|
||||
|
||||
// connection
|
||||
public LocalDateTime lastDataTime;
|
||||
public long lastConnection = 0L;
|
||||
public long previousConnection = 0L; // here should be stored last connection of previous session (so needs to be
|
||||
// read before lastConnection is modified for first time).
|
||||
|
||||
// last bolus
|
||||
public Date lastBolusTime;
|
||||
public Double lastBolusAmount;
|
||||
|
||||
// other pump settings
|
||||
public String activeProfileName = "0";
|
||||
public double reservoirRemainingUnits = 0d;
|
||||
public String reservoirFullUnits = "???";
|
||||
public int batteryRemaining = 0; // percent, so 0-100
|
||||
public Double batteryVoltage = null;
|
||||
|
||||
|
||||
// iob
|
||||
public String iob = null;
|
||||
|
||||
// TDD
|
||||
public Double dailyTotalUnits;
|
||||
public String maxDailyTotalUnits;
|
||||
public boolean validBasalRateProfileSelectedOnPump = true;
|
||||
public PumpType pumpType = PumpType.GenericAAPS;
|
||||
public ProfileStore profileStore;
|
||||
public String units; // Constants.MGDL or Constants.MMOL
|
||||
public PumpStatusType pumpStatusType = PumpStatusType.Running;
|
||||
public Double[] basalsByHour;
|
||||
public double currentBasal = 0;
|
||||
public int tempBasalInProgress = 0;
|
||||
public int tempBasalRatio = 0;
|
||||
public int tempBasalRemainMin = 0;
|
||||
public Date tempBasalStart;
|
||||
protected PumpDescription pumpDescription;
|
||||
|
||||
|
||||
public PumpStatus(PumpDescription pumpDescription) {
|
||||
this.pumpDescription = pumpDescription;
|
||||
|
||||
this.initSettings();
|
||||
}
|
||||
|
||||
|
||||
public abstract void initSettings();
|
||||
|
||||
|
||||
public void setLastCommunicationToNow() {
|
||||
this.lastDataTime = LocalDateTime.now();
|
||||
this.lastConnection = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
public abstract String getErrorInfo();
|
||||
|
||||
|
||||
public abstract void refreshConfiguration();
|
||||
|
||||
|
||||
public PumpType getPumpType() {
|
||||
return pumpType;
|
||||
}
|
||||
|
||||
|
||||
public void setPumpType(PumpType pumpType) {
|
||||
this.pumpType = pumpType;
|
||||
}
|
||||
|
||||
// public Date last_bolus_time;
|
||||
// public double last_bolus_amount = 0;
|
||||
|
||||
}
|
|
@ -91,4 +91,4 @@ public enum DoseStepSize
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public enum PumpCapability {
|
|||
DanaCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, TDD, ManualTDDLoad), //
|
||||
DanaWithHistoryCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, StoreCarbInfo, TDD, ManualTDDLoad), //
|
||||
InsightCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill,TDD,BasalRate30min), //
|
||||
|
||||
MedtronicCapabilities(Bolus, TempBasal, BasalProfileSet, Refill, TDD), //
|
||||
|
||||
// BasalRates (separately grouped)
|
||||
BasalRate_Duration15minAllowed, //
|
||||
|
@ -69,4 +69,4 @@ public enum PumpCapability {
|
|||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 10/15/18.
|
||||
*/
|
||||
|
||||
public enum PumpDriverState {
|
||||
|
||||
NotInitialized, //
|
||||
Connecting, //
|
||||
Connected, //
|
||||
Initialized, //
|
||||
Ready,
|
||||
Busy, //
|
||||
Suspended, //
|
||||
;
|
||||
|
||||
public static boolean isConnected(PumpDriverState pumpState) {
|
||||
return pumpState == Connected || pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isInitialized(PumpDriverState pumpState) {
|
||||
return pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/12/18.
|
||||
*/
|
||||
|
||||
public enum PumpStatusType {
|
||||
Running("normal"), //
|
||||
Suspended("suspended") //
|
||||
;
|
||||
|
||||
private String statusString;
|
||||
|
||||
|
||||
PumpStatusType(String statusString) {
|
||||
this.statusString = statusString;
|
||||
}
|
||||
|
||||
|
||||
public String getStatus() {
|
||||
return statusString;
|
||||
}
|
||||
}
|
|
@ -101,8 +101,8 @@ public enum PumpType {
|
|||
Medtronic_512_712("Medtronic 512/712", ManufacturerType.Medtronic, "512/712", 0.05d, null, //
|
||||
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), // TODO
|
||||
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.05d, 0.05d, null, PumpCapability.MedtronicCapabilities), //
|
||||
|
||||
Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
|
||||
Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
|
||||
|
@ -110,8 +110,8 @@ public enum PumpType {
|
|||
Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", ManufacturerType.Medtronic, "523/723 (Revel)", 0.05d, null, //
|
||||
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), //
|
||||
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.MedtronicCapabilities), //
|
||||
|
||||
Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.dialog;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
public interface RefreshableInterface {
|
||||
|
||||
void refreshData();
|
||||
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.Manifest;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanCallback;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.LocationHelper;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example)
|
||||
public class RileyLinkBLEScanActivity extends AppCompatActivity {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RileyLinkBLEScanActivity.class);
|
||||
|
||||
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary.
|
||||
private static final int REQUEST_ENABLE_BT = 30242; // arbitrary
|
||||
|
||||
private static String TAG = "RileyLinkBLEScanActivity";
|
||||
|
||||
// Stops scanning after 30 seconds.
|
||||
private static final long SCAN_PERIOD = 30000;
|
||||
public boolean mScanning;
|
||||
public ScanSettings settings;
|
||||
public List<ScanFilter> filters;
|
||||
public ListView listBTScan;
|
||||
public Toolbar toolbarBTScan;
|
||||
public Context mContext = this;
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
private BluetoothLeScanner mLEScanner;
|
||||
private LeDeviceListAdapter mLeDeviceListAdapter;
|
||||
private Handler mHandler;
|
||||
|
||||
private String actionTitleStart, actionTitleStop;
|
||||
private MenuItem menuItem;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.rileylink_scan_activity);
|
||||
|
||||
// Initializes Bluetooth adapter.
|
||||
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
mHandler = new Handler();
|
||||
|
||||
mLeDeviceListAdapter = new LeDeviceListAdapter();
|
||||
listBTScan = (ListView)findViewById(R.id.rileylink_listBTScan);
|
||||
listBTScan.setAdapter(mLeDeviceListAdapter);
|
||||
listBTScan.setOnItemClickListener((parent, view, position, id) -> {
|
||||
|
||||
// stop scanning if still active
|
||||
if (mScanning) {
|
||||
mScanning = false;
|
||||
mLEScanner.stopScan(mScanCallback2);
|
||||
}
|
||||
|
||||
TextView textview = (TextView)view.findViewById(R.id.rileylink_device_address);
|
||||
String bleAddress = textview.getText().toString();
|
||||
|
||||
SP.putString(RileyLinkConst.Prefs.RileyLinkAddress, bleAddress);
|
||||
|
||||
RileyLinkUtil.getRileyLinkSelectPreference().setSummary(bleAddress);
|
||||
|
||||
MedtronicPumpStatus pumpStatus = MedtronicUtil.getPumpStatus();
|
||||
pumpStatus.verifyConfiguration(); // force reloading of address
|
||||
|
||||
MainApp.bus().post(new EventMedtronicPumpConfigurationChanged());
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
toolbarBTScan = (Toolbar)findViewById(R.id.rileylink_toolbarBTScan);
|
||||
toolbarBTScan.setTitle(R.string.rileylink_scanner_title);
|
||||
setSupportActionBar(toolbarBTScan);
|
||||
|
||||
prepareForScanning();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_rileylink_ble_scan, menu);
|
||||
|
||||
actionTitleStart = MainApp.gs(R.string.rileylink_scanner_scan_scan);
|
||||
actionTitleStop = MainApp.gs(R.string.rileylink_scanner_scan_stop);
|
||||
|
||||
menuItem = menu.getItem(0);
|
||||
|
||||
menuItem.setTitle(actionTitleStart);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.rileylink_miScan: {
|
||||
scanLeDevice(menuItem.getTitle().equals(actionTitleStart));
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void prepareForScanning() {
|
||||
// https://developer.android.com/training/permissions/requesting.html
|
||||
// http://developer.radiusnetworks.com/2015/09/29/is-your-beacon-app-ready-for-android-6.html
|
||||
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
|
||||
Toast.makeText(this, R.string.rileylink_scanner_ble_not_supported, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// Use this check to determine whether BLE is supported on the device. Then
|
||||
// you can selectively disable BLE-related features.
|
||||
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
// your code that requires permission
|
||||
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION },
|
||||
PERMISSION_REQUEST_COARSE_LOCATION);
|
||||
}
|
||||
|
||||
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||
// displays a dialog requesting user permission to enable Bluetooth.
|
||||
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
|
||||
Toast.makeText(this, R.string.rileylink_scanner_ble_not_enabled, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Will request that GPS be enabled for devices running Marshmallow or newer.
|
||||
if (!LocationHelper.isLocationEnabled(this)) {
|
||||
LocationHelper.requestLocationForBluetooth(this);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
|
||||
}
|
||||
|
||||
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
|
||||
filters = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
|
||||
ParcelUuid.fromString(GattAttributes.SERVICE_RADIO)).build());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// disable currently selected RL, so that we can discover it
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnect);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_ENABLE_BT) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
// User allowed Bluetooth to turn on
|
||||
} else if (resultCode == RESULT_CANCELED) {
|
||||
// Error, or user said "NO"
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScanCallback mScanCallback2 = new ScanCallback() {
|
||||
|
||||
@Override
|
||||
public void onScanResult(int callbackType, final ScanResult scanRecord) {
|
||||
|
||||
Log.d(TAG, scanRecord.toString());
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if (addDevice(scanRecord))
|
||||
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(final List<ScanResult> results) {
|
||||
|
||||
runOnUiThread(() -> {
|
||||
|
||||
boolean added = false;
|
||||
|
||||
for (ScanResult result : results) {
|
||||
if (addDevice(result))
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (added)
|
||||
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private boolean addDevice(ScanResult result) {
|
||||
|
||||
BluetoothDevice device = result.getDevice();
|
||||
|
||||
List<ParcelUuid> serviceUuids = result.getScanRecord().getServiceUuids();
|
||||
|
||||
if (serviceUuids == null || serviceUuids.size() == 0) {
|
||||
Log.v(TAG, "Device " + device.getAddress() + " has no serviceUuids (Not RileyLink).");
|
||||
} else if (serviceUuids.size() > 1) {
|
||||
Log.v(TAG, "Device " + device.getAddress() + " has too many serviceUuids (Not RileyLink).");
|
||||
} else {
|
||||
|
||||
String uuid = serviceUuids.get(0).getUuid().toString().toLowerCase();
|
||||
|
||||
if (uuid.equals(GattAttributes.SERVICE_RADIO)) {
|
||||
Log.i(TAG, "Found RileyLink with address: " + device.getAddress());
|
||||
mLeDeviceListAdapter.addDevice(result);
|
||||
return true;
|
||||
} else {
|
||||
Log.v(TAG, "Device " + device.getAddress() + " has incorrect uuid (Not RileyLink).");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private String getDeviceDebug(BluetoothDevice device) {
|
||||
return "BluetoothDevice [name=" + device.getName() + ", address=" + device.getAddress() + //
|
||||
", type=" + device.getType(); // + ", alias=" + device.getAlias();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onScanFailed(int errorCode) {
|
||||
Log.e("Scan Failed", "Error Code: " + errorCode);
|
||||
Toast.makeText(mContext, MainApp.gs(R.string.rileylink_scanner_scanning_error, errorCode),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
private void scanLeDevice(final boolean enable) {
|
||||
|
||||
if (mLEScanner == null)
|
||||
return;
|
||||
|
||||
if (enable) {
|
||||
|
||||
mLeDeviceListAdapter.clear();
|
||||
mLeDeviceListAdapter.notifyDataSetChanged();
|
||||
|
||||
// Stops scanning after a pre-defined scan period.
|
||||
mHandler.postDelayed(() -> {
|
||||
|
||||
if (mScanning) {
|
||||
mScanning = false;
|
||||
mLEScanner.stopScan(mScanCallback2);
|
||||
LOG.debug("scanLeDevice: Scanning Stop");
|
||||
Toast.makeText(mContext, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
|
||||
menuItem.setTitle(actionTitleStart);
|
||||
}
|
||||
}, SCAN_PERIOD);
|
||||
|
||||
mScanning = true;
|
||||
mLEScanner.startScan(filters, settings, mScanCallback2);
|
||||
LOG.debug("scanLeDevice: Scanning Start");
|
||||
Toast.makeText(this, R.string.rileylink_scanner_scanning, Toast.LENGTH_SHORT).show();
|
||||
|
||||
menuItem.setTitle(actionTitleStop);
|
||||
|
||||
} else {
|
||||
if (mScanning) {
|
||||
mScanning = false;
|
||||
mLEScanner.stopScan(mScanCallback2);
|
||||
|
||||
LOG.debug("scanLeDevice: Scanning Stop");
|
||||
Toast.makeText(this, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
|
||||
|
||||
menuItem.setTitle(actionTitleStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LeDeviceListAdapter extends BaseAdapter {
|
||||
|
||||
private ArrayList<BluetoothDevice> mLeDevices;
|
||||
private Map<BluetoothDevice, Integer> rileyLinkDevices;
|
||||
private LayoutInflater mInflator;
|
||||
String currentlySelectedAddress;
|
||||
|
||||
|
||||
public LeDeviceListAdapter() {
|
||||
super();
|
||||
mLeDevices = new ArrayList<>();
|
||||
rileyLinkDevices = new HashMap<>();
|
||||
mInflator = RileyLinkBLEScanActivity.this.getLayoutInflater();
|
||||
currentlySelectedAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, "");
|
||||
}
|
||||
|
||||
|
||||
public void addDevice(ScanResult result) {
|
||||
|
||||
if (!mLeDevices.contains(result.getDevice())) {
|
||||
mLeDevices.add(result.getDevice());
|
||||
}
|
||||
rileyLinkDevices.put(result.getDevice(), result.getRssi());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
mLeDevices.clear();
|
||||
rileyLinkDevices.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mLeDevices.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getItem(int i) {
|
||||
return mLeDevices.get(i);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getItemId(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||
|
||||
ViewHolder viewHolder;
|
||||
// General ListView optimization code.
|
||||
if (view == null) {
|
||||
view = mInflator.inflate(R.layout.rileylink_scan_item, null);
|
||||
viewHolder = new ViewHolder();
|
||||
viewHolder.deviceAddress = (TextView)view.findViewById(R.id.rileylink_device_address);
|
||||
viewHolder.deviceName = (TextView)view.findViewById(R.id.rileylink_device_name);
|
||||
view.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder)view.getTag();
|
||||
}
|
||||
|
||||
BluetoothDevice device = mLeDevices.get(i);
|
||||
String deviceName = device.getName();
|
||||
|
||||
if (StringUtils.isBlank(deviceName)) {
|
||||
deviceName = "RileyLink";
|
||||
}
|
||||
|
||||
deviceName += " [" + rileyLinkDevices.get(device).intValue() + "]";
|
||||
|
||||
if (currentlySelectedAddress.equals(device.getAddress())) {
|
||||
// viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light));
|
||||
// viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light));
|
||||
deviceName += " (" + getResources().getString(R.string.rileylink_scanner_selected_device) + ")";
|
||||
}
|
||||
|
||||
viewHolder.deviceName.setText(deviceName);
|
||||
viewHolder.deviceAddress.setText(device.getAddress());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
|
||||
TextView deviceName;
|
||||
TextView deviceAddress;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyScanResults;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyTrial;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
/**
|
||||
* This is abstract class for RileyLink Communication, this one needs to be extended by specific "Pump" class.
|
||||
* <p>
|
||||
* Created by andy on 5/10/18.
|
||||
*/
|
||||
public abstract class RileyLinkCommunicationManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||
|
||||
private static final int SCAN_TIMEOUT = 1500;
|
||||
private static final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
protected final RFSpy rfspy;
|
||||
protected final Context context;
|
||||
protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation
|
||||
protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy))
|
||||
protected long lastGoodReceiverCommunicationTime = 0;
|
||||
protected PumpStatus pumpStatus;
|
||||
protected RileyLinkServiceData rileyLinkServiceData;
|
||||
private long nextWakeUpRequired = 0L;
|
||||
|
||||
// internal flag
|
||||
private boolean showPumpMessages = true;
|
||||
private int timeoutCount = 0;
|
||||
|
||||
|
||||
public RileyLinkCommunicationManager(Context context, RFSpy rfspy) {
|
||||
this.context = context;
|
||||
this.rfspy = rfspy;
|
||||
this.rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||
RileyLinkUtil.setRileyLinkCommunicationManager(this);
|
||||
|
||||
configurePumpSpecificSettings();
|
||||
}
|
||||
|
||||
|
||||
protected abstract void configurePumpSpecificSettings();
|
||||
|
||||
|
||||
// All pump communications go through this function.
|
||||
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Class<E> clazz)
|
||||
throws RileyLinkCommunicationException {
|
||||
|
||||
if (showPumpMessages) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData()));
|
||||
}
|
||||
|
||||
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), timeout_ms);
|
||||
|
||||
RadioResponse radioResponse = rfSpyResponse.getRadioResponse();
|
||||
|
||||
E response = createResponseMessage(rfSpyResponse.getRadioResponse().getPayload(), clazz);
|
||||
|
||||
if (response.isValid()) {
|
||||
// Mark this as the last time we heard from the pump.
|
||||
rememberLastGoodDeviceCommunicationTime();
|
||||
} else {
|
||||
LOG.warn("isDeviceReachable. Response is invalid ! [interrupted={}, timeout={}, unknownCommand={}, invalidParam={}]", rfSpyResponse.wasInterrupted(),
|
||||
rfSpyResponse.wasTimeout(), rfSpyResponse.isUnknownCommand(), rfSpyResponse.isInvalidParam());
|
||||
|
||||
if (rfSpyResponse.wasTimeout()) {
|
||||
timeoutCount++;
|
||||
|
||||
long diff = System.currentTimeMillis() - pumpStatus.lastConnection;
|
||||
|
||||
if (diff > ALLOWED_PUMP_UNREACHABLE) {
|
||||
LOG.warn("We reached max time that Pump can be unreachable. Starting Tuning.");
|
||||
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
|
||||
timeoutCount = 0;
|
||||
}
|
||||
|
||||
throw new RileyLinkCommunicationException(RileyLinkBLEError.Timeout);
|
||||
} else if (rfSpyResponse.wasInterrupted()) {
|
||||
throw new RileyLinkCommunicationException(RileyLinkBLEError.Interrupted);
|
||||
}
|
||||
}
|
||||
|
||||
if (showPumpMessages) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Received:" + ByteUtil.shortHexString(rfSpyResponse.getRadioResponse().getPayload()));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
public abstract <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz);
|
||||
|
||||
|
||||
public void wakeUp(boolean force) {
|
||||
wakeUp(receiverDeviceAwakeForMinutes, force);
|
||||
}
|
||||
|
||||
|
||||
public int getNotConnectedCount() {
|
||||
return rfspy != null ? rfspy.notConnectedCount : 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// FIXME change wakeup
|
||||
// TODO we might need to fix this. Maybe make pump awake for shorter time (battery factor for pump) - Andy
|
||||
public void wakeUp(int duration_minutes, boolean force) {
|
||||
// If it has been longer than n minutes, do wakeup. Otherwise assume pump is still awake.
|
||||
// **** FIXME: this wakeup doesn't seem to work well... must revisit
|
||||
// receiverDeviceAwakeForMinutes = duration_minutes;
|
||||
|
||||
MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp);
|
||||
|
||||
if (force)
|
||||
nextWakeUpRequired = 0L;
|
||||
|
||||
if (System.currentTimeMillis() > nextWakeUpRequired) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Waking pump...");
|
||||
|
||||
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple
|
||||
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)200,
|
||||
(byte)0, (byte)0, 25000, (byte)0);
|
||||
if (isLogEnabled())
|
||||
LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
|
||||
// FIXME wakeUp successful !!!!!!!!!!!!!!!!!!
|
||||
|
||||
nextWakeUpRequired = System.currentTimeMillis() + (receiverDeviceAwakeForMinutes * 60 * 1000);
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.trace("Last pump communication was recent, not waking pump.");
|
||||
}
|
||||
|
||||
// long lastGoodPlus = getLastGoodReceiverCommunicationTime() + (receiverDeviceAwakeForMinutes * 60 * 1000);
|
||||
//
|
||||
// if (System.currentTimeMillis() > lastGoodPlus || force) {
|
||||
// LOG.info("Waking pump...");
|
||||
//
|
||||
// byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.PowerOn);
|
||||
// RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, (byte)
|
||||
// 0, (byte) 0, 15000, (byte) 0);
|
||||
// LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
// } else {
|
||||
// LOG.trace("Last pump communication was recent, not waking pump.");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
public void setRadioFrequencyForPump(double freqMHz) {
|
||||
rfspy.setBaseFrequency(freqMHz);
|
||||
}
|
||||
|
||||
|
||||
public double tuneForDevice() {
|
||||
return scanForDevice(RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If user changes pump and one pump is running in US freq, and other in WW, then previously set frequency would be
|
||||
* invalid,
|
||||
* so we would need to retune. This checks that saved frequency is correct range.
|
||||
*
|
||||
* @param frequency
|
||||
* @return
|
||||
*/
|
||||
public boolean isValidFrequency(double frequency) {
|
||||
|
||||
double[] scanFrequencies = RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies();
|
||||
|
||||
if (scanFrequencies.length == 1) {
|
||||
return RileyLinkUtil.isSame(scanFrequencies[0], frequency);
|
||||
} else {
|
||||
return (scanFrequencies[0] <= frequency && scanFrequencies[scanFrequencies.length - 1] >= frequency);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Do device connection, with wakeup
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract boolean tryToConnectToDevice();
|
||||
|
||||
|
||||
public double scanForDevice(double[] frequencies) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Scanning for receiver ({})", receiverDeviceID);
|
||||
wakeUp(receiverDeviceAwakeForMinutes, false);
|
||||
FrequencyScanResults results = new FrequencyScanResults();
|
||||
|
||||
for (int i = 0; i < frequencies.length; i++) {
|
||||
int tries = 3;
|
||||
FrequencyTrial trial = new FrequencyTrial();
|
||||
trial.frequencyMHz = frequencies[i];
|
||||
rfspy.setBaseFrequency(frequencies[i]);
|
||||
|
||||
int sumRSSI = 0;
|
||||
for (int j = 0; j < tries; j++) {
|
||||
|
||||
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
|
||||
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)0,
|
||||
(byte)0, (byte)0, 1250, (byte)0);
|
||||
if (resp.wasTimeout()) {
|
||||
LOG.error("scanForPump: Failed to find pump at frequency {}", frequencies[i]);
|
||||
} else if (resp.looksLikeRadioPacket()) {
|
||||
RadioResponse radioResponse = new RadioResponse();
|
||||
|
||||
try {
|
||||
|
||||
radioResponse.init(resp.getRaw());
|
||||
|
||||
if (radioResponse.isValid()) {
|
||||
int rssi = calculateRssi(radioResponse.rssi);
|
||||
sumRSSI += rssi;
|
||||
trial.rssiList.add(rssi);
|
||||
trial.successes++;
|
||||
} else {
|
||||
LOG.warn("Failed to parse radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
trial.rssiList.add(-99);
|
||||
}
|
||||
|
||||
} catch (RileyLinkCommunicationException rle) {
|
||||
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
trial.rssiList.add(-99);
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG.error("scanForPump: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
trial.rssiList.add(-99);
|
||||
}
|
||||
trial.tries++;
|
||||
}
|
||||
sumRSSI += -99.0 * (trial.tries - trial.successes);
|
||||
trial.averageRSSI2 = (double)(sumRSSI) / (double)(trial.tries);
|
||||
|
||||
trial.calculateAverage();
|
||||
|
||||
results.trials.add(trial);
|
||||
}
|
||||
|
||||
results.dateTime = System.currentTimeMillis();
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder("Scan results:\n");
|
||||
|
||||
for (int k = 0; k < results.trials.size(); k++) {
|
||||
FrequencyTrial one = results.trials.get(k);
|
||||
|
||||
stringBuilder.append(String.format("Scan Result[%s]: Freq=%s, avg RSSI = %s\n", "" + k, ""
|
||||
+ one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList));
|
||||
}
|
||||
|
||||
LOG.info(stringBuilder.toString());
|
||||
|
||||
results.sort(); // sorts in ascending order
|
||||
|
||||
FrequencyTrial bestTrial = results.trials.get(results.trials.size() - 1);
|
||||
results.bestFrequencyMHz = bestTrial.frequencyMHz;
|
||||
if (bestTrial.successes > 0) {
|
||||
rfspy.setBaseFrequency(results.bestFrequencyMHz);
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Best frequency found: " + results.bestFrequencyMHz);
|
||||
return results.bestFrequencyMHz;
|
||||
} else {
|
||||
LOG.error("No pump response during scan.");
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int calculateRssi(int rssiIn) {
|
||||
int rssiOffset = 73;
|
||||
int outRssi = 0;
|
||||
if (rssiIn >= 128) {
|
||||
outRssi = ((rssiIn - 256) / 2) - rssiOffset;
|
||||
} else {
|
||||
outRssi = (rssiIn / 2) - rssiOffset;
|
||||
}
|
||||
|
||||
return outRssi;
|
||||
}
|
||||
|
||||
|
||||
public abstract byte[] createPumpMessageContent(RLMessageType type);
|
||||
|
||||
|
||||
private int tune_tryFrequency(double freqMHz) {
|
||||
rfspy.setBaseFrequency(freqMHz);
|
||||
// RLMessage msg = makeRLMessage(RLMessageType.ReadSimpleData);
|
||||
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
|
||||
RadioPacket pkt = new RadioPacket(pumpMsgContent);
|
||||
RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte)0, (byte)0, (byte)0, (byte)0, SCAN_TIMEOUT, (byte)0);
|
||||
if (resp.wasTimeout()) {
|
||||
LOG.warn("tune_tryFrequency: no pump response at frequency {}", freqMHz);
|
||||
} else if (resp.looksLikeRadioPacket()) {
|
||||
RadioResponse radioResponse = new RadioResponse();
|
||||
try {
|
||||
radioResponse.init(resp.getRaw());
|
||||
|
||||
if (radioResponse.isValid()) {
|
||||
LOG.warn("tune_tryFrequency: saw response level {} at frequency {}", radioResponse.rssi, freqMHz);
|
||||
return calculateRssi(radioResponse.rssi);
|
||||
} else {
|
||||
LOG.warn("tune_tryFrequency: invalid radio response:"
|
||||
+ ByteUtil.shortHexString(radioResponse.getPayload()));
|
||||
}
|
||||
|
||||
} catch (RileyLinkCommunicationException e) {
|
||||
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public double quickTuneForPump(double startFrequencyMHz) {
|
||||
double betterFrequency = startFrequencyMHz;
|
||||
double stepsize = 0.05;
|
||||
for (int tries = 0; tries < 4; tries++) {
|
||||
double evenBetterFrequency = quickTunePumpStep(betterFrequency, stepsize);
|
||||
if (evenBetterFrequency == 0.0) {
|
||||
// could not see the pump at all.
|
||||
// Try again at larger step size
|
||||
stepsize += 0.05;
|
||||
} else {
|
||||
if ((int)(evenBetterFrequency * 100) == (int)(betterFrequency * 100)) {
|
||||
// value did not change, so we're done.
|
||||
break;
|
||||
}
|
||||
betterFrequency = evenBetterFrequency; // and go again.
|
||||
}
|
||||
}
|
||||
if (betterFrequency == 0.0) {
|
||||
// we've failed... caller should try a full scan for pump
|
||||
if (isLogEnabled())
|
||||
LOG.error("quickTuneForPump: failed to find pump");
|
||||
} else {
|
||||
rfspy.setBaseFrequency(betterFrequency);
|
||||
if (betterFrequency != startFrequencyMHz) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("quickTuneForPump: new frequency is {}MHz", betterFrequency);
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.info("quickTuneForPump: pump frequency is the same: {}MHz", startFrequencyMHz);
|
||||
}
|
||||
}
|
||||
return betterFrequency;
|
||||
}
|
||||
|
||||
|
||||
private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Doing quick radio tune for receiver ({})", receiverDeviceID);
|
||||
wakeUp(false);
|
||||
int startRssi = tune_tryFrequency(startFrequencyMHz);
|
||||
double lowerFrequency = startFrequencyMHz - stepSizeMHz;
|
||||
int lowerRssi = tune_tryFrequency(lowerFrequency);
|
||||
double higherFrequency = startFrequencyMHz + stepSizeMHz;
|
||||
int higherRssi = tune_tryFrequency(higherFrequency);
|
||||
|
||||
if ((higherRssi == 0.0) && (lowerRssi == 0.0) && (startRssi == 0.0)) {
|
||||
// we can't see the pump at all...
|
||||
return 0.0;
|
||||
}
|
||||
if (higherRssi > startRssi) {
|
||||
// need to move higher
|
||||
return higherFrequency;
|
||||
} else if (lowerRssi > startRssi) {
|
||||
// need to move lower.
|
||||
return lowerFrequency;
|
||||
}
|
||||
return startFrequencyMHz;
|
||||
}
|
||||
|
||||
|
||||
protected void rememberLastGoodDeviceCommunicationTime() {
|
||||
lastGoodReceiverCommunicationTime = System.currentTimeMillis();
|
||||
|
||||
SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime);
|
||||
pumpStatus.setLastCommunicationToNow();
|
||||
}
|
||||
|
||||
|
||||
private long getLastGoodReceiverCommunicationTime() {
|
||||
// If we have a value of zero, we need to load from prefs.
|
||||
if (lastGoodReceiverCommunicationTime == 0L) {
|
||||
lastGoodReceiverCommunicationTime = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L);
|
||||
// Might still be zero, but that's fine.
|
||||
}
|
||||
double minutesAgo = (System.currentTimeMillis() - lastGoodReceiverCommunicationTime) / (1000.0 * 60.0);
|
||||
if (isLogEnabled())
|
||||
LOG.trace("Last good pump communication was " + minutesAgo + " minutes ago.");
|
||||
return lastGoodReceiverCommunicationTime;
|
||||
}
|
||||
|
||||
|
||||
public PumpStatus getPumpStatus() {
|
||||
return pumpStatus;
|
||||
}
|
||||
|
||||
|
||||
public void clearNotConnectedCount() {
|
||||
if (rfspy != null) {
|
||||
rfspy.notConnectedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPCOMM);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
|
||||
/**
|
||||
* Created by andy on 16/05/2018.
|
||||
*/
|
||||
|
||||
public class RileyLinkConst {
|
||||
|
||||
static final String Prefix = "AAPS.RileyLink.";
|
||||
|
||||
public class Intents {
|
||||
|
||||
public static final String RileyLinkReady = Prefix + "RileyLink_Ready";
|
||||
public static final String RileyLinkGattFailed = Prefix + "RileyLink_Gatt_Failed";
|
||||
|
||||
public static final String BluetoothConnected = Prefix + "Bluetooth_Connected";
|
||||
public static final String BluetoothReconnected = Prefix + "Bluetooth_Reconnected";
|
||||
public static final String BluetoothDisconnected = Prefix + "Bluetooth_Disconnected";
|
||||
public static final String RileyLinkDisconnected = Prefix + "RileyLink_Disconnected";
|
||||
|
||||
public static final String RileyLinkNewAddressSet = Prefix + "NewAddressSet";
|
||||
|
||||
public static final String INTENT_NEW_rileylinkAddressKey = Prefix + "INTENT_NEW_rileylinkAddressKey";
|
||||
public static final String INTENT_NEW_pumpIDKey = Prefix + "INTENT_NEW_pumpIDKey";
|
||||
public static final String RileyLinkDisconnect = Prefix + "RileyLink_Disconnect";
|
||||
}
|
||||
|
||||
public class Prefs {
|
||||
|
||||
//public static final String PrefPrefix = "pref_rileylink_";
|
||||
//public static final String RileyLinkAddress = PrefPrefix + "mac_address"; // pref_rileylink_mac_address
|
||||
public static final int RileyLinkAddress = R.string.pref_key_rileylink_mac_address;
|
||||
public static final String LastGoodDeviceCommunicationTime = Prefix + "lastGoodDeviceCommunicationTime";
|
||||
public static final String LastGoodDeviceFrequency = Prefix + "LastGoodDeviceFrequency";
|
||||
}
|
||||
|
||||
public class IPC {
|
||||
|
||||
// needs to br renamed (and maybe removed)
|
||||
public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune";
|
||||
public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump";
|
||||
public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory";
|
||||
public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory";
|
||||
|
||||
public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6b;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6bGeoff;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.BleAdvertisedData;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceNotification;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask;
|
||||
import info.nightscout.androidaps.plugins.pump.common.ui.RileyLinkSelectPreference;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange;
|
||||
|
||||
/**
|
||||
* Created by andy on 17/05/2018.
|
||||
*/
|
||||
|
||||
public class RileyLinkUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||
protected static List<RLHistoryItem> historyRileyLink = new ArrayList<>();
|
||||
protected static RileyLinkCommunicationManager rileyLinkCommunicationManager;
|
||||
static ServiceTask currentTask;
|
||||
private static Context context;
|
||||
private static RileyLinkBLE rileyLinkBLE;
|
||||
private static RileyLinkServiceData rileyLinkServiceData;
|
||||
private static RileyLinkService rileyLinkService;
|
||||
private static RileyLinkTargetFrequency rileyLinkTargetFrequency;
|
||||
|
||||
private static RileyLinkTargetDevice targetDevice;
|
||||
private static RileyLinkEncodingType encoding;
|
||||
private static RileyLinkSelectPreference rileyLinkSelectPreference;
|
||||
private static Encoding4b6b encoding4b6b;
|
||||
private static RileyLinkFirmwareVersion firmwareVersion;
|
||||
|
||||
|
||||
public static void setContext(Context contextIn) {
|
||||
RileyLinkUtil.context = contextIn;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkEncodingType getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
|
||||
public static void setEncoding(RileyLinkEncodingType encoding) {
|
||||
RileyLinkUtil.encoding = encoding;
|
||||
|
||||
if (encoding == RileyLinkEncodingType.FourByteSixByteLocal) {
|
||||
RileyLinkUtil.encoding4b6b = new Encoding4b6bGeoff();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void sendBroadcastMessage(String message) {
|
||||
if (context != null) {
|
||||
Intent intent = new Intent(message);
|
||||
LocalBroadcastManager.getInstance(RileyLinkUtil.context).sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void setServiceState(RileyLinkServiceState newState) {
|
||||
setServiceState(newState, null);
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkError getError() {
|
||||
if (RileyLinkUtil.rileyLinkServiceData != null)
|
||||
return RileyLinkUtil.rileyLinkServiceData.errorCode;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkServiceState getServiceState() {
|
||||
return workWithServiceState(null, null, false);
|
||||
}
|
||||
|
||||
|
||||
public static void setServiceState(RileyLinkServiceState newState, RileyLinkError errorCode) {
|
||||
workWithServiceState(newState, errorCode, true);
|
||||
}
|
||||
|
||||
|
||||
private static synchronized RileyLinkServiceState workWithServiceState(RileyLinkServiceState newState,
|
||||
RileyLinkError errorCode, boolean set) {
|
||||
|
||||
if (set) {
|
||||
|
||||
RileyLinkUtil.rileyLinkServiceData.serviceState = newState;
|
||||
RileyLinkUtil.rileyLinkServiceData.errorCode = errorCode;
|
||||
|
||||
if (L.isEnabled(L.PUMP))
|
||||
LOG.info("RileyLink State Changed: {} {}", newState, errorCode == null ? "" : " - Error State: "
|
||||
+ errorCode.name());
|
||||
|
||||
RileyLinkUtil.historyRileyLink.add(new RLHistoryItem(RileyLinkUtil.rileyLinkServiceData.serviceState,
|
||||
RileyLinkUtil.rileyLinkServiceData.errorCode, targetDevice));
|
||||
MainApp.bus().post(new EventMedtronicDeviceStatusChange(newState, errorCode));
|
||||
return null;
|
||||
|
||||
} else {
|
||||
return (RileyLinkUtil.rileyLinkServiceData == null || RileyLinkUtil.rileyLinkServiceData.serviceState == null) ? //
|
||||
RileyLinkServiceState.NotStarted
|
||||
: RileyLinkUtil.rileyLinkServiceData.serviceState;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkBLE getRileyLinkBLE() {
|
||||
return RileyLinkUtil.rileyLinkBLE;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkBLE(RileyLinkBLE rileyLinkBLEIn) {
|
||||
RileyLinkUtil.rileyLinkBLE = rileyLinkBLEIn;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkServiceData getRileyLinkServiceData() {
|
||||
return RileyLinkUtil.rileyLinkServiceData;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkServiceData(RileyLinkServiceData rileyLinkServiceData) {
|
||||
RileyLinkUtil.rileyLinkServiceData = rileyLinkServiceData;
|
||||
}
|
||||
|
||||
|
||||
public static boolean hasPumpBeenTunned() {
|
||||
return RileyLinkUtil.rileyLinkServiceData.tuneUpDone;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkService getRileyLinkService() {
|
||||
return RileyLinkUtil.rileyLinkService;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkService(RileyLinkService rileyLinkService) {
|
||||
RileyLinkUtil.rileyLinkService = rileyLinkService;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkCommunicationManager getRileyLinkCommunicationManager() {
|
||||
return RileyLinkUtil.rileyLinkCommunicationManager;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkCommunicationManager(RileyLinkCommunicationManager rileyLinkCommunicationManager) {
|
||||
RileyLinkUtil.rileyLinkCommunicationManager = rileyLinkCommunicationManager;
|
||||
}
|
||||
|
||||
|
||||
public static boolean sendNotification(ServiceNotification notification, Integer clientHashcode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// FIXME remove ?
|
||||
public static void setCurrentTask(ServiceTask task) {
|
||||
if (currentTask == null) {
|
||||
currentTask = task;
|
||||
} else {
|
||||
//LOG.error("setCurrentTask: Cannot replace current task");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void finishCurrentTask(ServiceTask task) {
|
||||
if (task != currentTask) {
|
||||
//LOG.error("finishCurrentTask: task does not match");
|
||||
}
|
||||
// hack to force deep copy of transport contents
|
||||
ServiceTransport transport = task.getServiceTransport().clone();
|
||||
|
||||
if (transport.hasServiceResult()) {
|
||||
sendServiceTransportResponse(transport, transport.getServiceResult());
|
||||
}
|
||||
currentTask = null;
|
||||
}
|
||||
|
||||
|
||||
public static void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) {
|
||||
// get the key (hashcode) of the client who requested this
|
||||
Integer clientHashcode = transport.getSenderHashcode();
|
||||
// make a new bundle to send as the message data
|
||||
transport.setServiceResult(serviceResult);
|
||||
// FIXME
|
||||
// transport.setTransportType(RT2Const.IPC.MSG_ServiceResult);
|
||||
// rileyLinkIPCConnection.sendTransport(transport, clientHashcode);
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkTargetFrequency getRileyLinkTargetFrequency() {
|
||||
return RileyLinkUtil.rileyLinkTargetFrequency;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkTargetFrequency(RileyLinkTargetFrequency rileyLinkTargetFrequency) {
|
||||
RileyLinkUtil.rileyLinkTargetFrequency = rileyLinkTargetFrequency;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isSame(Double d1, Double d2) {
|
||||
double diff = d1 - d2;
|
||||
|
||||
return (Math.abs(diff) <= 0.000001);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) {
|
||||
List<UUID> uuids = new ArrayList<UUID>();
|
||||
String name = null;
|
||||
if (advertisedData == null) {
|
||||
return new BleAdvertisedData(uuids, name);
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (buffer.remaining() > 2) {
|
||||
byte length = buffer.get();
|
||||
if (length == 0)
|
||||
break;
|
||||
|
||||
byte type = buffer.get();
|
||||
switch (type) {
|
||||
case 0x02: // Partial list of 16-bit UUIDs
|
||||
case 0x03: // Complete list of 16-bit UUIDs
|
||||
while (length >= 2) {
|
||||
uuids
|
||||
.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
|
||||
length -= 2;
|
||||
}
|
||||
break;
|
||||
case 0x06: // Partial list of 128-bit UUIDs
|
||||
case 0x07: // Complete list of 128-bit UUIDs
|
||||
while (length >= 16) {
|
||||
long lsb = buffer.getLong();
|
||||
long msb = buffer.getLong();
|
||||
uuids.add(new UUID(msb, lsb));
|
||||
length -= 16;
|
||||
}
|
||||
break;
|
||||
case 0x09:
|
||||
byte[] nameBytes = new byte[length - 1];
|
||||
buffer.get(nameBytes);
|
||||
try {
|
||||
name = new String(nameBytes, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
buffer.position(buffer.position() + length - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new BleAdvertisedData(uuids, name);
|
||||
}
|
||||
|
||||
|
||||
public static List<RLHistoryItem> getRileyLinkHistory() {
|
||||
return historyRileyLink;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkTargetDevice getTargetDevice() {
|
||||
return targetDevice;
|
||||
}
|
||||
|
||||
|
||||
public static void setTargetDevice(RileyLinkTargetDevice targetDevice) {
|
||||
RileyLinkUtil.targetDevice = targetDevice;
|
||||
}
|
||||
|
||||
|
||||
public static void setRileyLinkSelectPreference(RileyLinkSelectPreference rileyLinkSelectPreference) {
|
||||
|
||||
RileyLinkUtil.rileyLinkSelectPreference = rileyLinkSelectPreference;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkSelectPreference getRileyLinkSelectPreference() {
|
||||
|
||||
return rileyLinkSelectPreference;
|
||||
}
|
||||
|
||||
|
||||
public static Encoding4b6b getEncoding4b6b() {
|
||||
return RileyLinkUtil.encoding4b6b;
|
||||
}
|
||||
|
||||
|
||||
public static void setFirmwareVersion(RileyLinkFirmwareVersion firmwareVersion) {
|
||||
RileyLinkUtil.firmwareVersion = firmwareVersion;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkFirmwareVersion getFirmwareVersion() {
|
||||
return firmwareVersion;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,438 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.Reset;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SendAndListen;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetHardwareEncoding;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetPreamble;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.UpdateRegister;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RXFilterMode;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class RFSpy {
|
||||
|
||||
public static final long RILEYLINK_FREQ_XTAL = 24000000;
|
||||
public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 7500; // 1500
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
public int notConnectedCount = 0;
|
||||
private RileyLinkBLE rileyLinkBle;
|
||||
private RFSpyReader reader;
|
||||
private RileyLinkTargetFrequency selectedTargetFrequency;
|
||||
private UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
|
||||
private UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
|
||||
private UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION);
|
||||
private UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT);
|
||||
private RileyLinkFirmwareVersion firmwareVersion;
|
||||
private String bleVersion; // We don't use it so no need of sofisticated logic
|
||||
Double currentFrequencyMHz;
|
||||
|
||||
|
||||
public RFSpy(RileyLinkBLE rileyLinkBle) {
|
||||
this.rileyLinkBle = rileyLinkBle;
|
||||
reader = new RFSpyReader(rileyLinkBle);
|
||||
}
|
||||
|
||||
|
||||
public RileyLinkFirmwareVersion getRLVersionCached() {
|
||||
return firmwareVersion;
|
||||
}
|
||||
|
||||
|
||||
public String getBLEVersionCached() {
|
||||
return bleVersion;
|
||||
}
|
||||
|
||||
|
||||
// Call this after the RL services are discovered.
|
||||
// Starts an async task to read when data is available
|
||||
public void startReader() {
|
||||
rileyLinkBle.registerRadioResponseCountNotification(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
newDataIsAvailable();
|
||||
}
|
||||
});
|
||||
reader.start();
|
||||
}
|
||||
|
||||
|
||||
// Here should go generic RL initialisation + protocol adjustments depending on
|
||||
// firmware version
|
||||
public void initializeRileyLink() {
|
||||
bleVersion = getVersion();
|
||||
firmwareVersion = getFirmwareVersion();
|
||||
RileyLinkUtil.setFirmwareVersion(firmwareVersion);
|
||||
}
|
||||
|
||||
|
||||
// Call this from the "response count" notification handler.
|
||||
public void newDataIsAvailable() {
|
||||
// pass the message to the reader (which should be internal to RFSpy)
|
||||
reader.newDataIsAvailable();
|
||||
}
|
||||
|
||||
|
||||
// This gets the version from the BLE113, not from the CC1110.
|
||||
// I.e., this gets the version from the BLE interface, not from the radio.
|
||||
public String getVersion() {
|
||||
BLECommOperationResult result = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID, radioVersionUUID);
|
||||
if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
|
||||
String version = StringUtil.fromBytes(result.value);
|
||||
if (isLogEnabled())
|
||||
LOG.debug("BLE Version: " + version);
|
||||
return version;
|
||||
} else {
|
||||
LOG.error("getVersion failed with code: " + result.resultCode);
|
||||
return "(null)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public RileyLinkFirmwareVersion getFirmwareVersion() {
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Firmware Version. Get Version - Start");
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
// We have to call raw version of communication to get firmware version
|
||||
// So that we can adjust other commands accordingly afterwords
|
||||
|
||||
byte[] getVersionRaw = getByteArray(RileyLinkCommandType.GetVersion.code);
|
||||
byte[] response = writeToDataRaw(getVersionRaw, 5000);
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Firmware Version. GetVersion [response={}]", ByteUtil.shortHexString(response));
|
||||
|
||||
if (response != null) { // && response[0] == (byte) 0xDD) {
|
||||
|
||||
String versionString = StringUtil.fromBytes(response);
|
||||
|
||||
RileyLinkFirmwareVersion version = RileyLinkFirmwareVersion.getByVersionString(StringUtil
|
||||
.fromBytes(response));
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.trace("Firmware Version string: {}, resolved to {}.", versionString, version);
|
||||
|
||||
if (version != RileyLinkFirmwareVersion.UnknownVersion)
|
||||
return version;
|
||||
|
||||
SystemClock.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.error("Firmware Version can't be determined. Checking with BLE Version [{}].", bleVersion);
|
||||
|
||||
if (bleVersion.contains(" 2.")) {
|
||||
return RileyLinkFirmwareVersion.Version_2_0;
|
||||
}
|
||||
|
||||
return RileyLinkFirmwareVersion.UnknownVersion;
|
||||
}
|
||||
|
||||
|
||||
private byte[] writeToDataRaw(byte[] bytes, int responseTimeout_ms) {
|
||||
SystemClock.sleep(100);
|
||||
// FIXME drain read queue?
|
||||
byte[] junkInBuffer = reader.poll(0);
|
||||
|
||||
while (junkInBuffer != null) {
|
||||
LOG.warn(ThreadUtil.sig() + "writeToData: draining read queue, found this: "
|
||||
+ ByteUtil.shortHexString(junkInBuffer));
|
||||
junkInBuffer = reader.poll(0);
|
||||
}
|
||||
|
||||
// prepend length, and send it.
|
||||
byte[] prepended = ByteUtil.concat(new byte[]{(byte) (bytes.length)}, bytes);
|
||||
|
||||
LOG.debug("writeToData (raw={})", ByteUtil.shortHexString(prepended));
|
||||
|
||||
BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID, radioDataUUID,
|
||||
prepended);
|
||||
if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
|
||||
LOG.error("BLE Write operation failed, code=" + writeCheck.resultCode);
|
||||
return null; // will be a null (invalid) response
|
||||
}
|
||||
SystemClock.sleep(100);
|
||||
// Log.i(TAG,ThreadUtil.sig()+String.format(" writeToData:(timeout %d) %s",(responseTimeout_ms),ByteUtil.shortHexString(prepended)));
|
||||
byte[] rawResponse = reader.poll(responseTimeout_ms);
|
||||
return rawResponse;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// The caller has to know how long the RFSpy will be busy with what was sent to it.
|
||||
private RFSpyResponse writeToData(RileyLinkCommand command, int responseTimeout_ms) {
|
||||
|
||||
byte[] bytes = command.getRaw();
|
||||
byte[] rawResponse = writeToDataRaw(bytes, responseTimeout_ms);
|
||||
|
||||
RFSpyResponse resp = new RFSpyResponse(command, rawResponse);
|
||||
if (rawResponse == null) {
|
||||
LOG.error("writeToData: No response from RileyLink");
|
||||
notConnectedCount++;
|
||||
} else {
|
||||
if (resp.wasInterrupted()) {
|
||||
LOG.error("writeToData: RileyLink was interrupted");
|
||||
} else if (resp.wasTimeout()) {
|
||||
LOG.error("writeToData: RileyLink reports timeout");
|
||||
notConnectedCount++;
|
||||
} else if (resp.isOK()) {
|
||||
LOG.warn("writeToData: RileyLink reports OK");
|
||||
resetNotConnectedCount();
|
||||
} else {
|
||||
if (resp.looksLikeRadioPacket()) {
|
||||
// RadioResponse radioResp = resp.getRadioResponse();
|
||||
// byte[] responsePayload = radioResp.getPayload();
|
||||
if (isLogEnabled())
|
||||
LOG.trace("writeToData: received radio response. Will decode at upper level");
|
||||
resetNotConnectedCount();
|
||||
}
|
||||
// Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse));
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
private void resetNotConnectedCount() {
|
||||
this.notConnectedCount = 0;
|
||||
}
|
||||
|
||||
|
||||
private byte[] getByteArray(byte... input) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
private byte[] getCommandArray(RileyLinkCommandType command, byte[] body) {
|
||||
int bodyLength = body == null ? 0 : body.length;
|
||||
|
||||
byte[] output = new byte[bodyLength + 1];
|
||||
|
||||
output[0] = command.code;
|
||||
|
||||
if (body != null) {
|
||||
for (int i = 0; i < body.length; i++) {
|
||||
output[i + 1] = body[i];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
|
||||
byte listenChannel, int timeout_ms, byte retryCount) {
|
||||
return transmitThenReceive(pkt, sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms, retryCount, null);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse transmitThenReceive(RadioPacket pkt, int timeout_ms) {
|
||||
return transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, timeout_ms, (byte) 0);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
|
||||
byte listenChannel, int timeout_ms, byte retryCount, Integer extendPreamble_ms) {
|
||||
|
||||
int sendDelay = repeatCount * delay_ms;
|
||||
int receiveDelay = timeout_ms * (retryCount + 1);
|
||||
|
||||
SendAndListen command = new SendAndListen(sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms,
|
||||
retryCount, extendPreamble_ms, pkt);
|
||||
|
||||
return writeToData(command, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse updateRegister(CC111XRegister reg, int val) {
|
||||
RFSpyResponse resp = writeToData(new UpdateRegister(reg, (byte) val), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
public void setBaseFrequency(double freqMHz) {
|
||||
int value = (int) (freqMHz * 1000000 / ((double) (RILEYLINK_FREQ_XTAL) / Math.pow(2.0, 16.0)));
|
||||
updateRegister(CC111XRegister.freq0, (byte) (value & 0xff));
|
||||
updateRegister(CC111XRegister.freq1, (byte) ((value >> 8) & 0xff));
|
||||
updateRegister(CC111XRegister.freq2, (byte) ((value >> 16) & 0xff));
|
||||
LOG.info("Set frequency to {} MHz", freqMHz);
|
||||
|
||||
this.currentFrequencyMHz = freqMHz;
|
||||
|
||||
configureRadioForRegion(RileyLinkUtil.getRileyLinkTargetFrequency());
|
||||
}
|
||||
|
||||
|
||||
private void configureRadioForRegion(RileyLinkTargetFrequency frequency) {
|
||||
|
||||
// we update registers only on first run, or if region changed
|
||||
|
||||
switch (frequency) {
|
||||
case Medtronic_WorldWide: {
|
||||
// updateRegister(CC111X_MDMCFG4, (byte) 0x59);
|
||||
setRXFilterMode(RXFilterMode.Wide);
|
||||
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
|
||||
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
|
||||
updateRegister(CC111XRegister.mdmcfg1, 0x62);
|
||||
updateRegister(CC111XRegister.mdmcfg0, 0x1A);
|
||||
updateRegister(CC111XRegister.deviatn, 0x13);
|
||||
setMedtronicEncoding();
|
||||
}
|
||||
break;
|
||||
|
||||
case Medtronic_US: {
|
||||
// updateRegister(CC111X_MDMCFG4, (byte) 0x99);
|
||||
setRXFilterMode(RXFilterMode.Narrow);
|
||||
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
|
||||
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
|
||||
updateRegister(CC111XRegister.mdmcfg1, 0x61);
|
||||
updateRegister(CC111XRegister.mdmcfg0, 0x7E);
|
||||
updateRegister(CC111XRegister.deviatn, 0x15);
|
||||
setMedtronicEncoding();
|
||||
}
|
||||
break;
|
||||
|
||||
case Omnipod: {
|
||||
RFSpyResponse r = null;
|
||||
// RL initialization for Omnipod is a copy/paste from OmniKit implementation.
|
||||
// Last commit from original repository: 5c3beb4144
|
||||
// so if something is terribly wrong, please check git diff PodCommsSession.swift since that commit
|
||||
r = updateRegister(CC111XRegister.pktctrl1, 0x20);
|
||||
r = updateRegister(CC111XRegister.agcctrl0, 0x00);
|
||||
r = updateRegister(CC111XRegister.fsctrl1, 0x06);
|
||||
r = updateRegister(CC111XRegister.mdmcfg4, 0xCA);
|
||||
r = updateRegister(CC111XRegister.mdmcfg3, 0xBC);
|
||||
r = updateRegister(CC111XRegister.mdmcfg2, 0x06);
|
||||
r = updateRegister(CC111XRegister.mdmcfg1, 0x70);
|
||||
r = updateRegister(CC111XRegister.mdmcfg0, 0x11);
|
||||
r = updateRegister(CC111XRegister.deviatn, 0x44);
|
||||
r = updateRegister(CC111XRegister.mcsm0, 0x18);
|
||||
r = updateRegister(CC111XRegister.foccfg, 0x17);
|
||||
r = updateRegister(CC111XRegister.fscal3, 0xE9);
|
||||
r = updateRegister(CC111XRegister.fscal2, 0x2A);
|
||||
r = updateRegister(CC111XRegister.fscal1, 0x00);
|
||||
r = updateRegister(CC111XRegister.fscal0, 0x1F);
|
||||
|
||||
r = updateRegister(CC111XRegister.test1, 0x31);
|
||||
r = updateRegister(CC111XRegister.test0, 0x09);
|
||||
r = updateRegister(CC111XRegister.paTable0, 0x84);
|
||||
r = updateRegister(CC111XRegister.sync1, 0xA5);
|
||||
r = updateRegister(CC111XRegister.sync0, 0x5A);
|
||||
|
||||
r = setRileyLinkEncoding(RileyLinkEncodingType.Manchester);
|
||||
r = setPreamble(0x6665);
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG.warn("No region configuration for RfSpy and {}", frequency.name());
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
this.selectedTargetFrequency = frequency;
|
||||
}
|
||||
|
||||
|
||||
private void setMedtronicEncoding() {
|
||||
RileyLinkEncodingType encoding = RileyLinkEncodingType.FourByteSixByteLocal;
|
||||
|
||||
if (RileyLinkFirmwareVersion.isSameVersion(this.firmwareVersion, RileyLinkFirmwareVersion.Version2AndHigher)) {
|
||||
if (SP.getString(MedtronicConst.Prefs.Encoding, "None").equals(MainApp.gs(R.string.medtronic_pump_encoding_4b6b_rileylink))) {
|
||||
encoding = RileyLinkEncodingType.FourByteSixByteRileyLink;
|
||||
}
|
||||
}
|
||||
|
||||
setRileyLinkEncoding(encoding);
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Set Encoding for Medtronic: " + encoding.name());
|
||||
}
|
||||
|
||||
|
||||
private RFSpyResponse setPreamble(int preamble) {
|
||||
RFSpyResponse resp = null;
|
||||
try {
|
||||
resp = writeToData(new SetPreamble(preamble), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||
} catch (Exception e) {
|
||||
e.toString();
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse setRileyLinkEncoding(RileyLinkEncodingType encoding) {
|
||||
RFSpyResponse resp = writeToData(new SetHardwareEncoding(encoding), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||
|
||||
if (resp.isOK()) {
|
||||
reader.setRileyLinkEncodingType(encoding);
|
||||
RileyLinkUtil.setEncoding(encoding);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
private void setRXFilterMode(RXFilterMode mode) {
|
||||
|
||||
byte drate_e = (byte) 0x9; // exponent of symbol rate (16kbps)
|
||||
byte chanbw = mode.value;
|
||||
|
||||
updateRegister(CC111XRegister.mdmcfg4, (byte) (chanbw | drate_e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset RileyLink Configuration (set all updateRegisters)
|
||||
*/
|
||||
public void resetRileyLinkConfiguration() {
|
||||
if (this.currentFrequencyMHz != null)
|
||||
this.setBaseFrequency(this.currentFrequencyMHz);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse resetRileyLink() {
|
||||
RFSpyResponse resp = null;
|
||||
try {
|
||||
resp = writeToData(new Reset(), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Reset command send, response: {}", resp);
|
||||
} catch (Exception e) {
|
||||
e.toString();
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPBTCOMM);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class RFSpyReader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
private static AsyncTask<Void, Void, Void> readerTask;
|
||||
private RileyLinkBLE rileyLinkBle;
|
||||
private Semaphore waitForRadioData = new Semaphore(0, true);
|
||||
private LinkedBlockingQueue<byte[]> mDataQueue = new LinkedBlockingQueue<>();
|
||||
private int acquireCount = 0;
|
||||
private int releaseCount = 0;
|
||||
private boolean stopAtNull = true;
|
||||
|
||||
|
||||
public RFSpyReader(RileyLinkBLE rileyLinkBle) {
|
||||
this.rileyLinkBle = rileyLinkBle;
|
||||
}
|
||||
|
||||
|
||||
public void init(RileyLinkBLE rileyLinkBLE) {
|
||||
this.rileyLinkBle = rileyLinkBLE;
|
||||
}
|
||||
|
||||
|
||||
public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) {
|
||||
if (readerTask != null) {
|
||||
readerTask.cancel(true);
|
||||
}
|
||||
this.rileyLinkBle = rileyLinkBle;
|
||||
}
|
||||
|
||||
public void setRileyLinkEncodingType(RileyLinkEncodingType encodingType) {
|
||||
stopAtNull = !(encodingType == RileyLinkEncodingType.Manchester || //
|
||||
encodingType == RileyLinkEncodingType.FourByteSixByteRileyLink);
|
||||
}
|
||||
|
||||
|
||||
// This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen.
|
||||
public byte[] poll(int timeout_ms) {
|
||||
if (isLogEnabled())
|
||||
LOG.trace(ThreadUtil.sig() + "Entering poll at t==" + SystemClock.uptimeMillis() + ", timeout is " + timeout_ms
|
||||
+ " mDataQueue size is " + mDataQueue.size());
|
||||
|
||||
if (mDataQueue.isEmpty())
|
||||
try {
|
||||
// block until timeout or data available.
|
||||
// returns null if timeout.
|
||||
byte[] dataFromQueue = mDataQueue.poll(timeout_ms, TimeUnit.MILLISECONDS);
|
||||
if (dataFromQueue != null) {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t=="
|
||||
+ SystemClock.uptimeMillis());
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Got data [null] at t==" + SystemClock.uptimeMillis());
|
||||
}
|
||||
return dataFromQueue;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("poll: Interrupted waiting for data");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Call this from the "response count" notification handler.
|
||||
public void newDataIsAvailable() {
|
||||
releaseCount++;
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.trace(ThreadUtil.sig() + "waitForRadioData released(count=" + releaseCount + ") at t="
|
||||
+ SystemClock.uptimeMillis());
|
||||
waitForRadioData.release();
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
readerTask = new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
UUID serviceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
|
||||
UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
|
||||
BLECommOperationResult result;
|
||||
while (true) {
|
||||
try {
|
||||
acquireCount++;
|
||||
waitForRadioData.acquire();
|
||||
if (isLogEnabled())
|
||||
LOG.trace(ThreadUtil.sig() + "waitForRadioData acquired (count=" + acquireCount + ") at t="
|
||||
+ SystemClock.uptimeMillis());
|
||||
SystemClock.sleep(100);
|
||||
SystemClock.sleep(1);
|
||||
result = rileyLinkBle.readCharacteristic_blocking(serviceUUID, radioDataUUID);
|
||||
SystemClock.sleep(100);
|
||||
|
||||
if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
|
||||
if (stopAtNull) {
|
||||
// only data up to the first null is valid
|
||||
for (int i = 0; i < result.value.length; i++) {
|
||||
if (result.value[i] == 0) {
|
||||
result.value = ByteUtil.substring(result.value, 0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mDataQueue.add(result.value);
|
||||
} else if (result.resultCode == BLECommOperationResult.RESULT_INTERRUPTED) {
|
||||
LOG.error("Read operation was interrupted");
|
||||
} else if (result.resultCode == BLECommOperationResult.RESULT_TIMEOUT) {
|
||||
LOG.error("Read operation on Radio Data timed out");
|
||||
} else if (result.resultCode == BLECommOperationResult.RESULT_BUSY) {
|
||||
LOG.error("FAIL: RileyLinkBLE reports operation already in progress");
|
||||
} else if (result.resultCode == BLECommOperationResult.RESULT_NONE) {
|
||||
LOG.error("FAIL: got invalid result code: " + result.resultCode);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Interrupted while waiting for data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPBTCOMM);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,572 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperation;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicReadOperation;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicWriteOperation;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.DescriptorWriteOperation;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
* Added: State handling, configuration of RF for different configuration ranges, connection handling
|
||||
*/
|
||||
public class RileyLinkBLE {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
|
||||
private final Context context;
|
||||
public boolean gattDebugEnabled = true;
|
||||
boolean manualDisconnect = false;
|
||||
private BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothGattCallback bluetoothGattCallback;
|
||||
private BluetoothDevice rileyLinkDevice;
|
||||
private BluetoothGatt bluetoothConnectionGatt = null;
|
||||
private BLECommOperation mCurrentOperation;
|
||||
private Semaphore gattOperationSema = new Semaphore(1, true);
|
||||
private Runnable radioResponseCountNotified;
|
||||
private boolean mIsConnected = false;
|
||||
|
||||
|
||||
public RileyLinkBLE(final Context context) {
|
||||
this.context = context;
|
||||
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.debug("BT Adapter: " + this.bluetoothAdapter);
|
||||
bluetoothGattCallback = new BluetoothGattCallback() {
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(final BluetoothGatt gatt,
|
||||
final BluetoothGattCharacteristic characteristic) {
|
||||
super.onCharacteristicChanged(gatt, characteristic);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.trace(ThreadUtil.sig() + "onCharacteristicChanged "
|
||||
+ GattAttributes.lookup(characteristic.getUuid()) + " "
|
||||
+ ByteUtil.getHex(characteristic.getValue()));
|
||||
if (characteristic.getUuid().equals(UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT))) {
|
||||
LOG.debug("Response Count is " + ByteUtil.shortHexString(characteristic.getValue()));
|
||||
}
|
||||
}
|
||||
if (radioResponseCountNotified != null) {
|
||||
radioResponseCountNotified.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead(final BluetoothGatt gatt,
|
||||
final BluetoothGattCharacteristic characteristic, int status) {
|
||||
super.onCharacteristicRead(gatt, characteristic, status);
|
||||
|
||||
final String statusMessage = getGattStatusMessage(status);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.trace(ThreadUtil.sig() + "onCharacteristicRead ("
|
||||
+ GattAttributes.lookup(characteristic.getUuid()) + ") " + statusMessage + ":"
|
||||
+ ByteUtil.getHex(characteristic.getValue()));
|
||||
}
|
||||
mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(final BluetoothGatt gatt,
|
||||
final BluetoothGattCharacteristic characteristic, int status) {
|
||||
super.onCharacteristicWrite(gatt, characteristic, status);
|
||||
|
||||
final String uuidString = GattAttributes.lookup(characteristic.getUuid());
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.trace(ThreadUtil.sig() + "onCharacteristicWrite " + getGattStatusMessage(status) + " "
|
||||
+ uuidString + " " + ByteUtil.shortHexString(characteristic.getValue()));
|
||||
}
|
||||
mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
|
||||
super.onConnectionStateChange(gatt, status, newState);
|
||||
|
||||
// https://github.com/NordicSemiconductor/puck-central-android/blob/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/GattManager.java#L117
|
||||
if (status == 133) {
|
||||
LOG.error("Got the status 133 bug, closing gatt");
|
||||
disconnect();
|
||||
SystemClock.sleep(500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gattDebugEnabled) {
|
||||
final String stateMessage;
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
stateMessage = "CONNECTED";
|
||||
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
|
||||
stateMessage = "CONNECTING";
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
stateMessage = "DISCONNECTED";
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
|
||||
stateMessage = "DISCONNECTING";
|
||||
} else {
|
||||
stateMessage = "UNKNOWN newState (" + newState + ")";
|
||||
}
|
||||
|
||||
if (isLogEnabled())
|
||||
LOG.warn("onConnectionStateChange " + getGattStatusMessage(status) + " " + stateMessage);
|
||||
}
|
||||
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothConnected);
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("BT State connected, GATT status {} ({})", status, getGattStatusMessage(status));
|
||||
}
|
||||
|
||||
} else if ((newState == BluetoothProfile.STATE_CONNECTING) || //
|
||||
(newState == BluetoothProfile.STATE_DISCONNECTING)) {
|
||||
// LOG.debug("We are in {} state.", status == BluetoothProfile.STATE_CONNECTING ? "Connecting" :
|
||||
// "Disconnecting");
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnected);
|
||||
if (manualDisconnect)
|
||||
close();
|
||||
LOG.warn("RileyLink Disconnected.");
|
||||
} else {
|
||||
LOG.warn("Some other state: (status={},newState={})", status, newState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
super.onDescriptorWrite(gatt, descriptor, status);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onDescriptorWrite " + GattAttributes.lookup(descriptor.getUuid()) + " "
|
||||
+ getGattStatusMessage(status) + " written: " + ByteUtil.getHex(descriptor.getValue()));
|
||||
}
|
||||
mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
super.onDescriptorRead(gatt, descriptor, status);
|
||||
mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue());
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onDescriptorRead " + getGattStatusMessage(status) + " status " + descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
super.onMtuChanged(gatt, mtu, status);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onMtuChanged " + mtu + " status " + status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReadRemoteRssi(final BluetoothGatt gatt, int rssi, int status) {
|
||||
super.onReadRemoteRssi(gatt, rssi, status);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onReadRemoteRssi " + getGattStatusMessage(status) + ": " + rssi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
super.onReliableWriteCompleted(gatt, status);
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onReliableWriteCompleted status " + status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
|
||||
super.onServicesDiscovered(gatt, status);
|
||||
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
final List<BluetoothGattService> services = gatt.getServices();
|
||||
|
||||
boolean rileyLinkFound = false;
|
||||
|
||||
for (BluetoothGattService service : services) {
|
||||
final UUID uuidService = service.getUuid();
|
||||
|
||||
if (isAnyRileyLinkServiceFound(service)) {
|
||||
rileyLinkFound = true;
|
||||
}
|
||||
|
||||
if (gattDebugEnabled) {
|
||||
debugService(service, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (gattDebugEnabled && isLogEnabled()) {
|
||||
LOG.warn("onServicesDiscovered " + getGattStatusMessage(status));
|
||||
}
|
||||
|
||||
LOG.info("Gatt device is RileyLink device: " + rileyLinkFound);
|
||||
|
||||
if (rileyLinkFound) {
|
||||
mIsConnected = true;
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkReady);
|
||||
// RileyLinkUtil.sendNotification(new
|
||||
// ServiceNotification(RileyLinkConst.Intents.RileyLinkReady), null);
|
||||
} else {
|
||||
mIsConnected = false;
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkError,
|
||||
RileyLinkError.DeviceIsNotRileyLink);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("onServicesDiscovered " + getGattStatusMessage(status));
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkGattFailed);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private boolean isAnyRileyLinkServiceFound(BluetoothGattService service) {
|
||||
|
||||
boolean found = false;
|
||||
|
||||
found = GattAttributes.isRileyLink(service.getUuid());
|
||||
|
||||
if (found) {
|
||||
return true;
|
||||
} else {
|
||||
List<BluetoothGattService> includedServices = service.getIncludedServices();
|
||||
|
||||
for (BluetoothGattService serviceI : includedServices) {
|
||||
if (isAnyRileyLinkServiceFound(serviceI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public BluetoothDevice getRileyLinkDevice() {
|
||||
return this.rileyLinkDevice;
|
||||
}
|
||||
|
||||
|
||||
public void debugService(BluetoothGattService service, int indentCount) {
|
||||
|
||||
String indentString = StringUtils.repeat(' ', indentCount);
|
||||
|
||||
final UUID uuidService = service.getUuid();
|
||||
|
||||
if (gattDebugEnabled) {
|
||||
final String uuidServiceString = uuidService.toString();
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append(indentString);
|
||||
stringBuilder.append(GattAttributes.lookup(uuidServiceString, "Unknown service"));
|
||||
stringBuilder.append(" (" + uuidServiceString + ")");
|
||||
|
||||
for (BluetoothGattCharacteristic character : service.getCharacteristics()) {
|
||||
final String uuidCharacteristicString = character.getUuid().toString();
|
||||
|
||||
stringBuilder.append("\n ");
|
||||
stringBuilder.append(indentString);
|
||||
stringBuilder.append(" - " + GattAttributes.lookup(uuidCharacteristicString, "Unknown Characteristic"));
|
||||
stringBuilder.append(" (" + uuidCharacteristicString + ")");
|
||||
}
|
||||
|
||||
stringBuilder.append("\n\n");
|
||||
|
||||
LOG.warn(stringBuilder.toString());
|
||||
|
||||
List<BluetoothGattService> includedServices = service.getIncludedServices();
|
||||
|
||||
for (BluetoothGattService serviceI : includedServices) {
|
||||
debugService(serviceI, indentCount + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void registerRadioResponseCountNotification(Runnable notifier) {
|
||||
radioResponseCountNotified = notifier;
|
||||
}
|
||||
|
||||
|
||||
public boolean isConnected() {
|
||||
return mIsConnected;
|
||||
}
|
||||
|
||||
|
||||
public boolean discoverServices() {
|
||||
|
||||
if (bluetoothConnectionGatt == null) {
|
||||
// shouldn't happen, but if it does we exit
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bluetoothConnectionGatt.discoverServices()) {
|
||||
if (isLogEnabled())
|
||||
LOG.warn("Starting to discover GATT Services.");
|
||||
return true;
|
||||
} else {
|
||||
LOG.error("Cannot discover GATT Services.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean enableNotifications() {
|
||||
BLECommOperationResult result = setNotification_blocking(UUID.fromString(GattAttributes.SERVICE_RADIO), //
|
||||
UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT));
|
||||
if (result.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
|
||||
LOG.error("Error setting response count notification");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void findRileyLink(String RileyLinkAddress) {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("RileyLink address: " + RileyLinkAddress);
|
||||
// Must verify that this is a valid MAC, or crash.
|
||||
|
||||
rileyLinkDevice = bluetoothAdapter.getRemoteDevice(RileyLinkAddress);
|
||||
// if this succeeds, we get a connection state change callback?
|
||||
connectGatt();
|
||||
}
|
||||
|
||||
|
||||
// This function must be run on UI thread.
|
||||
public void connectGatt() {
|
||||
bluetoothConnectionGatt = rileyLinkDevice.connectGatt(context, true, bluetoothGattCallback);
|
||||
// , BluetoothDevice.TRANSPORT_LE
|
||||
if (bluetoothConnectionGatt == null) {
|
||||
LOG.error("Failed to connect to Bluetooth Low Energy device at " + bluetoothAdapter.getAddress());
|
||||
} else {
|
||||
if (gattDebugEnabled) {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Gatt Connected?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void disconnect() {
|
||||
mIsConnected = false;
|
||||
if (isLogEnabled())
|
||||
LOG.warn("Closing GATT connection");
|
||||
// Close old conenction
|
||||
if (bluetoothConnectionGatt != null) {
|
||||
// Not sure if to disconnect or to close first..
|
||||
bluetoothConnectionGatt.disconnect();
|
||||
manualDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
if (bluetoothConnectionGatt != null) {
|
||||
bluetoothConnectionGatt.close();
|
||||
bluetoothConnectionGatt = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BLECommOperationResult setNotification_blocking(UUID serviceUUID, UUID charaUUID) {
|
||||
BLECommOperationResult rval = new BLECommOperationResult();
|
||||
if (bluetoothConnectionGatt != null) {
|
||||
|
||||
try {
|
||||
gattOperationSema.acquire();
|
||||
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("setNotification_blocking: interrupted waiting for gattOperationSema");
|
||||
return rval;
|
||||
}
|
||||
if (mCurrentOperation != null) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||
} else {
|
||||
if (bluetoothConnectionGatt.getService(serviceUUID) == null) {
|
||||
// Catch if the service is not supported by the BLE device
|
||||
rval.resultCode = BLECommOperationResult.RESULT_NONE;
|
||||
LOG.error("BT Device not supported");
|
||||
// TODO: 11/07/2016 UI update for user
|
||||
} else {
|
||||
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID)
|
||||
.getCharacteristic(charaUUID);
|
||||
// Tell Android that we want the notifications
|
||||
bluetoothConnectionGatt.setCharacteristicNotification(chara, true);
|
||||
List<BluetoothGattDescriptor> list = chara.getDescriptors();
|
||||
if (gattDebugEnabled) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Found descriptor: " + list.get(i).toString());
|
||||
}
|
||||
}
|
||||
BluetoothGattDescriptor descr = list.get(0);
|
||||
// Tell the remote device to send the notifications
|
||||
mCurrentOperation = new DescriptorWriteOperation(bluetoothConnectionGatt, descr,
|
||||
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
||||
mCurrentOperation.execute(this);
|
||||
if (mCurrentOperation.timedOut) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||
} else if (mCurrentOperation.interrupted) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||
} else {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
mCurrentOperation = null;
|
||||
gattOperationSema.release();
|
||||
}
|
||||
} else {
|
||||
LOG.error("setNotification_blocking: not configured!");
|
||||
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
// call from main
|
||||
public BLECommOperationResult writeCharacteristic_blocking(UUID serviceUUID, UUID charaUUID, byte[] value) {
|
||||
BLECommOperationResult rval = new BLECommOperationResult();
|
||||
if (bluetoothConnectionGatt != null) {
|
||||
rval.value = value;
|
||||
try {
|
||||
gattOperationSema.acquire();
|
||||
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("writeCharacteristic_blocking: interrupted waiting for gattOperationSema");
|
||||
return rval;
|
||||
}
|
||||
|
||||
if (mCurrentOperation != null) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||
} else {
|
||||
if (bluetoothConnectionGatt.getService(serviceUUID) == null) {
|
||||
// Catch if the service is not supported by the BLE device
|
||||
// GGW: Tue Jul 12 01:14:01 UTC 2016: This can also happen if the
|
||||
// app that created the bluetoothConnectionGatt has been destroyed/created,
|
||||
// e.g. when the user switches from portrait to landscape.
|
||||
rval.resultCode = BLECommOperationResult.RESULT_NONE;
|
||||
LOG.error("BT Device not supported");
|
||||
// TODO: 11/07/2016 UI update for user
|
||||
} else {
|
||||
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID)
|
||||
.getCharacteristic(charaUUID);
|
||||
mCurrentOperation = new CharacteristicWriteOperation(bluetoothConnectionGatt, chara, value);
|
||||
mCurrentOperation.execute(this);
|
||||
if (mCurrentOperation.timedOut) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||
} else if (mCurrentOperation.interrupted) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||
} else {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
mCurrentOperation = null;
|
||||
gattOperationSema.release();
|
||||
}
|
||||
} else {
|
||||
LOG.error("writeCharacteristic_blocking: not configured!");
|
||||
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
public BLECommOperationResult readCharacteristic_blocking(UUID serviceUUID, UUID charaUUID) {
|
||||
BLECommOperationResult rval = new BLECommOperationResult();
|
||||
if (bluetoothConnectionGatt != null) {
|
||||
try {
|
||||
gattOperationSema.acquire();
|
||||
SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("readCharacteristic_blocking: Interrupted waiting for gattOperationSema");
|
||||
return rval;
|
||||
}
|
||||
if (mCurrentOperation != null) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_BUSY;
|
||||
} else {
|
||||
BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic(
|
||||
charaUUID);
|
||||
mCurrentOperation = new CharacteristicReadOperation(bluetoothConnectionGatt, chara);
|
||||
mCurrentOperation.execute(this);
|
||||
if (mCurrentOperation.timedOut) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT;
|
||||
} else if (mCurrentOperation.interrupted) {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED;
|
||||
} else {
|
||||
rval.resultCode = BLECommOperationResult.RESULT_SUCCESS;
|
||||
rval.value = mCurrentOperation.getValue();
|
||||
}
|
||||
}
|
||||
mCurrentOperation = null;
|
||||
gattOperationSema.release();
|
||||
} else {
|
||||
LOG.error("readCharacteristic_blocking: not configured!");
|
||||
rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
private String getGattStatusMessage(final int status) {
|
||||
final String statusMessage;
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
statusMessage = "SUCCESS";
|
||||
} else if (status == BluetoothGatt.GATT_FAILURE) {
|
||||
statusMessage = "FAILED";
|
||||
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
|
||||
statusMessage = "NOT PERMITTED";
|
||||
} else if (status == 133) {
|
||||
statusMessage = "Found the strange 133 bug";
|
||||
} else {
|
||||
statusMessage = "UNKNOWN (" + status + ")";
|
||||
}
|
||||
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPBTCOMM);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/23/18.
|
||||
*/
|
||||
|
||||
public class RileyLinkCommunicationException extends Exception {
|
||||
|
||||
String extendedErrorText;
|
||||
private RileyLinkBLEError errorCode;
|
||||
|
||||
|
||||
public RileyLinkCommunicationException(RileyLinkBLEError errorCode, String extendedErrorText) {
|
||||
super(errorCode.getDescription());
|
||||
|
||||
this.errorCode = errorCode;
|
||||
this.extendedErrorText = extendedErrorText;
|
||||
}
|
||||
|
||||
|
||||
public RileyLinkCommunicationException(RileyLinkBLEError errorCode) {
|
||||
super(errorCode.getDescription());
|
||||
|
||||
this.errorCode = errorCode;
|
||||
// this.extendedErrorText = extendedErrorText;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
|
||||
public class GetVersion extends RileyLinkCommand {
|
||||
|
||||
public GetVersion() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.GetVersion;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
return super.getRawSimple();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
|
||||
public class Reset extends RileyLinkCommand {
|
||||
|
||||
public Reset() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.Reset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
return super.getRawSimple();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
|
||||
public class ResetRadioConfig extends RileyLinkCommand {
|
||||
|
||||
public ResetRadioConfig() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.ResetRadioConfig;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
return super.getRawSimple();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
|
||||
public abstract class RileyLinkCommand {
|
||||
|
||||
public RileyLinkCommand() {
|
||||
}
|
||||
|
||||
|
||||
public abstract RileyLinkCommandType getCommandType();
|
||||
|
||||
|
||||
public abstract byte[] getRaw();
|
||||
|
||||
|
||||
protected byte[] getRawSimple() {
|
||||
return getByteArray(getCommandType().code);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected byte[] getByteArray(byte... input) {
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
|
||||
public class SendAndListen extends RileyLinkCommand {
|
||||
|
||||
private byte sendChannel;
|
||||
private byte repeatCount;
|
||||
private int delayBetweenPackets_ms;
|
||||
private byte listenChannel;
|
||||
private int timeout_ms;
|
||||
private byte retryCount;
|
||||
private Integer preambleExtension_ms;
|
||||
private RadioPacket packetToSend;
|
||||
|
||||
|
||||
public SendAndListen(byte sendChannel, byte repeatCount, byte delayBetweenPackets_ms, byte listenChannel,
|
||||
int timeout_ms, byte retryCount, RadioPacket packetToSend
|
||||
|
||||
) {
|
||||
this(sendChannel, repeatCount, delayBetweenPackets_ms, listenChannel, timeout_ms, retryCount, null,
|
||||
packetToSend);
|
||||
}
|
||||
|
||||
|
||||
public SendAndListen(byte sendChannel, byte repeatCount, int delayBetweenPackets_ms, byte listenChannel,
|
||||
int timeout_ms, byte retryCount, Integer preambleExtension_ms, RadioPacket packetToSend
|
||||
|
||||
) {
|
||||
super();
|
||||
this.sendChannel = sendChannel;
|
||||
this.repeatCount = repeatCount;
|
||||
this.delayBetweenPackets_ms = delayBetweenPackets_ms;
|
||||
this.listenChannel = listenChannel;
|
||||
this.timeout_ms = timeout_ms;
|
||||
this.retryCount = retryCount;
|
||||
this.preambleExtension_ms = preambleExtension_ms == null ? 0 : preambleExtension_ms;
|
||||
this.packetToSend = packetToSend;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.SendAndListen;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
|
||||
// If firmware version is not set (error reading version from device, shouldn't happen),
|
||||
// we will default to version 2
|
||||
boolean isPacketV2 = RileyLinkUtil.getFirmwareVersion() != null ? RileyLinkUtil.getFirmwareVersion()
|
||||
.isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher) : true;
|
||||
|
||||
ArrayList<Byte> bytes = new ArrayList<Byte>();
|
||||
bytes.add(this.getCommandType().code);
|
||||
bytes.add(this.sendChannel);
|
||||
bytes.add(this.repeatCount);
|
||||
|
||||
if (isPacketV2) { // delay is unsigned 16-bit integer
|
||||
byte[] delayBuff = ByteBuffer.allocate(4).putInt(delayBetweenPackets_ms).array();
|
||||
bytes.add(delayBuff[2]);
|
||||
bytes.add(delayBuff[3]);
|
||||
} else {
|
||||
bytes.add((byte)delayBetweenPackets_ms);
|
||||
}
|
||||
|
||||
bytes.add(this.listenChannel);
|
||||
|
||||
byte[] timeoutBuff = ByteBuffer.allocate(4).putInt(timeout_ms).array();
|
||||
|
||||
bytes.add(timeoutBuff[0]);
|
||||
bytes.add(timeoutBuff[1]);
|
||||
bytes.add(timeoutBuff[2]);
|
||||
bytes.add(timeoutBuff[3]);
|
||||
|
||||
bytes.add(retryCount);
|
||||
|
||||
if (isPacketV2) { // 2.x (and probably higher versions) support preamble extension
|
||||
byte[] preambleBuf = ByteBuffer.allocate(4).putInt(preambleExtension_ms).array();
|
||||
bytes.add(preambleBuf[2]);
|
||||
bytes.add(preambleBuf[3]);
|
||||
}
|
||||
|
||||
return ByteUtil.concat(ByteUtil.getByteArrayFromList(bytes), packetToSend.getEncoded());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||
|
||||
public class SetHardwareEncoding extends RileyLinkCommand {
|
||||
|
||||
private final RileyLinkEncodingType encoding;
|
||||
|
||||
|
||||
public SetHardwareEncoding(RileyLinkEncodingType encoding) {
|
||||
super();
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.SetHardwareEncoding;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
return getByteArray(getCommandType().code, encoding.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
|
||||
public class SetPreamble extends RileyLinkCommand {
|
||||
|
||||
private int preamble;
|
||||
|
||||
|
||||
public SetPreamble(int preamble) throws Exception {
|
||||
super();
|
||||
|
||||
// this command was not supported before 2.0
|
||||
if (!RileyLinkUtil.getFirmwareVersion().isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher)) {
|
||||
throw new NotImplementedException("Old firmware does not support SetPreamble command");
|
||||
}
|
||||
|
||||
if (preamble < 0 || preamble > 0xFFFF) {
|
||||
throw new Exception("preamble value is out of range");
|
||||
}
|
||||
this.preamble = preamble;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.SetPreamble;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
byte[] bytes = ByteBuffer.allocate(4).putInt(preamble).array();
|
||||
return getByteArray(this.getCommandType().code, bytes[2], bytes[3]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
|
||||
public class UpdateRegister extends RileyLinkCommand {
|
||||
|
||||
CC111XRegister register;
|
||||
byte registerValue;
|
||||
|
||||
|
||||
public UpdateRegister(CC111XRegister register, byte registerValue) {
|
||||
super();
|
||||
this.register = register;
|
||||
this.registerValue = registerValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RileyLinkCommandType getCommandType() {
|
||||
return RileyLinkCommandType.UpdateRegister;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getRaw() {
|
||||
return getByteArray(getCommandType().code, register.value, registerValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/30/16.
|
||||
* changed by Andy 10/20/18
|
||||
*/
|
||||
public class FrequencyScanResults {
|
||||
|
||||
public List<FrequencyTrial> trials = new ArrayList<>();
|
||||
public double bestFrequencyMHz = 0.0;
|
||||
public long dateTime;
|
||||
|
||||
|
||||
public void sort() {
|
||||
Collections.sort(trials, (trial1, trial2) -> {
|
||||
int res = trial1.averageRSSI.compareTo(trial2.averageRSSI);
|
||||
|
||||
if (res == 0) {
|
||||
return (int)(trial1.frequencyMHz - trial2.frequencyMHz);
|
||||
} else
|
||||
return res;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/30/16.
|
||||
* changed by Andy 10/20/18
|
||||
*/
|
||||
public class FrequencyTrial {
|
||||
|
||||
public int tries = 0;
|
||||
public int successes = 0;
|
||||
public Double averageRSSI = 0.0;
|
||||
public double frequencyMHz = 0.0;
|
||||
public List<Integer> rssiList = new ArrayList<>();
|
||||
public double averageRSSI2;
|
||||
|
||||
|
||||
public void calculateAverage() {
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
for (Integer rssi : rssiList) {
|
||||
sum += Math.abs(rssi);
|
||||
count++;
|
||||
}
|
||||
|
||||
double avg = (sum / (count * 1.0d));
|
||||
|
||||
if (count != 0)
|
||||
this.averageRSSI = avg * (-1);
|
||||
else
|
||||
this.averageRSSI = -99.0d;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/21/16.
|
||||
*/
|
||||
public class GattAttributes {
|
||||
|
||||
// NOTE: these uuid strings must be lower case!
|
||||
|
||||
public static String PREFIX = "0000";
|
||||
public static String SUFFIX = "-0000-1000-8000-00805f9b34fb";
|
||||
public static String SERVICE_GAP = PREFIX + "1800" + SUFFIX;
|
||||
public static String CHARA_GAP_NAME = PREFIX + "2a00" + SUFFIX; // RileyLink RFSpy
|
||||
public static String CHARA_GAP_NUM = PREFIX + "2a01" + SUFFIX; // 0000
|
||||
public static String CHARA_GAP_UNK = PREFIX + "2a01" + SUFFIX; // a
|
||||
|
||||
public static String SERVICE_BATTERY = PREFIX + "180f" + SUFFIX; // Battery
|
||||
public static String CHARA_BATTERY_UNK = PREFIX + "2a19" + SUFFIX;
|
||||
|
||||
// RileyLink Radio Service
|
||||
public static String SERVICE_RADIO = "0235733b-99c5-4197-b856-69219c2a3845";
|
||||
public static String CHARA_RADIO_DATA = "c842e849-5028-42e2-867c-016adada9155";
|
||||
public static String CHARA_RADIO_RESPONSE_COUNT = "6e6c7910-b89e-43a5-a0fe-50c5e2b81f4a";
|
||||
public static String CHARA_RADIO_TIMER_TICK = "6e6c7910-b89e-43a5-78af-50c5e2b86f7e";
|
||||
public static String CHARA_RADIO_CUSTOM_NAME = "d93b2af0-1e28-11e4-8c21-0800200c9a66";
|
||||
public static String CHARA_RADIO_VERSION = "30d99dc9-7c91-4295-a051-0a104d238cf2";
|
||||
public static String CHARA_RADIO_LED_MODE = "c6d84241-f1a7-4f9c-a25f-fce16732f14e";
|
||||
|
||||
private static Map<String, String> attributes;
|
||||
private static Map<String, String> attributesRileyLinkSpecific;
|
||||
|
||||
// table of names for uuids
|
||||
static {
|
||||
attributes = new HashMap<>();
|
||||
|
||||
attributes.put(SERVICE_GAP, "Device Information Service");
|
||||
attributes.put(CHARA_GAP_NAME, "Name"); //
|
||||
attributes.put(CHARA_GAP_NUM, "Number"); //
|
||||
|
||||
attributes.put(SERVICE_BATTERY, "Battery Service");
|
||||
|
||||
attributes.put(SERVICE_RADIO, "Radio Interface"); // a
|
||||
attributes.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name");
|
||||
attributes.put(CHARA_RADIO_DATA, "Data");
|
||||
attributes.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count");
|
||||
attributes.put(CHARA_RADIO_TIMER_TICK, "Timer Tick");
|
||||
attributes.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion
|
||||
attributes.put(CHARA_RADIO_LED_MODE, "Led Mode");
|
||||
|
||||
attributesRileyLinkSpecific = new HashMap<>();
|
||||
|
||||
attributesRileyLinkSpecific.put(SERVICE_RADIO, "Radio Interface"); // a
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name");
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_DATA, "Data");
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count");
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_TIMER_TICK, "Timer Tick");
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion
|
||||
attributesRileyLinkSpecific.put(CHARA_RADIO_LED_MODE, "Led Mode");
|
||||
}
|
||||
|
||||
|
||||
public static String lookup(UUID uuid) {
|
||||
return lookup(uuid.toString());
|
||||
}
|
||||
|
||||
|
||||
public static String lookup(String uuid) {
|
||||
return lookup(uuid, uuid);
|
||||
}
|
||||
|
||||
|
||||
public static String lookup(String uuid, String defaultName) {
|
||||
String name = attributes.get(uuid);
|
||||
return name == null ? defaultName : name;
|
||||
}
|
||||
|
||||
|
||||
// we check for specific UUID (Radio ones, because thoose seem to be unique
|
||||
public static boolean isRileyLink(UUID uuid) {
|
||||
return attributesRileyLinkSpecific.containsKey(uuid.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RFSpyRLResponse;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class RFSpyResponse {
|
||||
|
||||
// 0xaa == timeout
|
||||
// 0xbb == interrupted
|
||||
// 0xcc == zero-data
|
||||
// 0xdd == success
|
||||
// 0x11 == invalidParam
|
||||
// 0x22 == unknownCommand
|
||||
|
||||
protected byte[] raw;
|
||||
protected RadioResponse radioResponse;
|
||||
private RileyLinkCommand command;
|
||||
|
||||
|
||||
public RFSpyResponse() {
|
||||
init(new byte[0]);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse(byte[] bytes) {
|
||||
init(bytes);
|
||||
}
|
||||
|
||||
|
||||
public RFSpyResponse(RileyLinkCommand command, byte[] rawResponse) {
|
||||
|
||||
this.command = command;
|
||||
init(rawResponse);
|
||||
}
|
||||
|
||||
|
||||
public void init(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
raw = new byte[0];
|
||||
} else {
|
||||
raw = bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public RadioResponse getRadioResponse() throws RileyLinkCommunicationException {
|
||||
if (looksLikeRadioPacket()) {
|
||||
radioResponse = new RadioResponse(command);
|
||||
radioResponse.init(raw);
|
||||
} else {
|
||||
radioResponse = new RadioResponse();
|
||||
}
|
||||
return radioResponse;
|
||||
}
|
||||
|
||||
|
||||
public boolean wasTimeout() {
|
||||
if ((raw.length == 1) || (raw.length == 2)) {
|
||||
if (raw[0] == (byte)0xaa) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean wasInterrupted() {
|
||||
if ((raw.length == 1) || (raw.length == 2)) {
|
||||
if (raw[0] == (byte)0xbb) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isInvalidParam() {
|
||||
if ((raw.length == 1) || (raw.length == 2)) {
|
||||
if (raw[0] == (byte)0x11) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isUnknownCommand() {
|
||||
if ((raw.length == 1) || (raw.length == 2)) {
|
||||
if (raw[0] == (byte)0x22) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isOK() {
|
||||
if ((raw.length == 1) || (raw.length == 2)) {
|
||||
if (raw[0] == (byte)0x01 || raw[0] == (byte)0xDD) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean looksLikeRadioPacket() {
|
||||
if (raw.length > 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (raw.length > 2) {
|
||||
return "Radio packet";
|
||||
} else {
|
||||
RFSpyRLResponse r = RFSpyRLResponse.fromByte(raw[0]);
|
||||
return r.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] getRaw() {
|
||||
return raw;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/6/18.
|
||||
*/
|
||||
public interface RLMessage {
|
||||
|
||||
byte[] getTxData();
|
||||
|
||||
|
||||
boolean isValid();
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/6/18.
|
||||
*/
|
||||
|
||||
public enum RLMessageType {
|
||||
PowerOn, // for powering on the pump (wakeup)
|
||||
ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel)
|
||||
;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.CRC;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/22/16.
|
||||
*/
|
||||
|
||||
public class RadioPacket {
|
||||
|
||||
protected byte[] pkt;
|
||||
|
||||
|
||||
public RadioPacket(byte[] pkt) {
|
||||
this.pkt = pkt;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getRaw() {
|
||||
return pkt;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getWithCRC() {
|
||||
byte[] withCRC = ByteUtil.concat(pkt, CRC.crc8(pkt));
|
||||
return withCRC;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getEncoded() {
|
||||
|
||||
switch (RileyLinkUtil.getEncoding()) {
|
||||
case Manchester: { // We have this encoding in RL firmware
|
||||
return pkt;
|
||||
}
|
||||
|
||||
case FourByteSixByteLocal: {
|
||||
byte[] withCRC = getWithCRC();
|
||||
|
||||
byte[] encoded = RileyLinkUtil.getEncoding4b6b().encode4b6b(withCRC);
|
||||
return ByteUtil.concat(encoded, (byte)0);
|
||||
}
|
||||
|
||||
case FourByteSixByteRileyLink: {
|
||||
return getWithCRC();
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException(("Encoding not supported: " + RileyLinkUtil.getEncoding().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.CRC;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/30/16.
|
||||
*/
|
||||
public class RadioResponse {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
|
||||
public boolean decodedOK = false;
|
||||
public int rssi;
|
||||
public int responseNumber;
|
||||
public byte[] decodedPayload = new byte[0];
|
||||
public byte receivedCRC;
|
||||
private RileyLinkCommand command;
|
||||
|
||||
|
||||
public RadioResponse() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// public RadioResponse(byte[] rxData) {
|
||||
// init(rxData);
|
||||
// }
|
||||
|
||||
public RadioResponse(RileyLinkCommand command /* , byte[] raw */) {
|
||||
this.command = command;
|
||||
// init(raw);
|
||||
}
|
||||
|
||||
|
||||
public boolean isValid() {
|
||||
|
||||
// We should check for all listening commands, but only one is actually used
|
||||
if (command != null && command.getCommandType() != RileyLinkCommandType.SendAndListen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!decodedOK) {
|
||||
return false;
|
||||
}
|
||||
if (decodedPayload != null) {
|
||||
if (receivedCRC == CRC.crc8(decodedPayload)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void init(byte[] rxData) throws RileyLinkCommunicationException {
|
||||
|
||||
if (rxData == null) {
|
||||
return;
|
||||
}
|
||||
if (rxData.length < 3) {
|
||||
// This does not look like something valid heard from a RileyLink device
|
||||
return;
|
||||
}
|
||||
byte[] encodedPayload;
|
||||
|
||||
if (RileyLinkFirmwareVersion.isSameVersion(RileyLinkUtil.getRileyLinkServiceData().versionCC110,
|
||||
RileyLinkFirmwareVersion.Version2)) {
|
||||
encodedPayload = ByteUtil.substring(rxData, 3, rxData.length - 3);
|
||||
rssi = rxData[1];
|
||||
responseNumber = rxData[2];
|
||||
} else {
|
||||
encodedPayload = ByteUtil.substring(rxData, 2, rxData.length - 2);
|
||||
rssi = rxData[0];
|
||||
responseNumber = rxData[1];
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// for non-radio commands we just return the raw response
|
||||
// well, for non-radio commands we shouldn't even reach this point
|
||||
// but getVersion is kind of exception
|
||||
if (command != null && //
|
||||
command.getCommandType() != RileyLinkCommandType.SendAndListen) {
|
||||
decodedOK = true;
|
||||
decodedPayload = encodedPayload;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (RileyLinkUtil.getEncoding()) {
|
||||
|
||||
case Manchester:
|
||||
case FourByteSixByteRileyLink: {
|
||||
decodedOK = true;
|
||||
decodedPayload = encodedPayload;
|
||||
}
|
||||
break;
|
||||
|
||||
case FourByteSixByteLocal: {
|
||||
byte[] decodeThis = RileyLinkUtil.getEncoding4b6b().decode4b6b(encodedPayload);
|
||||
|
||||
if (decodeThis != null && decodeThis.length > 2) {
|
||||
decodedOK = true;
|
||||
|
||||
decodedPayload = ByteUtil.substring(decodeThis, 0, decodeThis.length - 1);
|
||||
receivedCRC = decodeThis[decodeThis.length - 1];
|
||||
byte calculatedCRC = CRC.crc8(decodedPayload);
|
||||
if (receivedCRC != calculatedCRC) {
|
||||
LOG.error(String.format("RadioResponse: CRC mismatch, calculated 0x%02x, received 0x%02x",
|
||||
calculatedCRC, receivedCRC));
|
||||
}
|
||||
} else {
|
||||
throw new RileyLinkCommunicationException(RileyLinkBLEError.TooShortOrNullResponse);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("this {" + RileyLinkUtil.getEncoding().toString()
|
||||
+ "} encoding is not supported");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
decodedOK = false;
|
||||
LOG.error("Failed to decode radio data: " + ByteUtil.shortHexString(encodedPayload));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] getPayload() {
|
||||
return decodedPayload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public interface Encoding4b6b {
|
||||
|
||||
byte[] encode4b6b(byte[] data);
|
||||
|
||||
|
||||
byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException;
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public abstract class Encoding4b6bAbstract implements Encoding4b6b {
|
||||
|
||||
/**
|
||||
* encode4b6bMap is an ordered list of translations 6bits -> 4 bits, in order from 0x0 to 0xF
|
||||
* The 6 bit codes are what is used on the RF side of the RileyLink to communicate
|
||||
* with a Medtronic pump.
|
||||
*/
|
||||
public static final byte[] encode4b6bList = new byte[] {
|
||||
0x15, 0x31, 0x32, 0x23, 0x34, 0x25, 0x26, 0x16, 0x1a, 0x19, 0x2a, 0x0b, 0x2c, 0x0d, 0x0e, 0x1c };
|
||||
|
||||
|
||||
// 21, 49, 50, 35, 52, 37, 38, 22, 26, 25, 42, 11, 44, 13, 14, 28
|
||||
|
||||
public abstract byte[] encode4b6b(byte[] data);
|
||||
|
||||
|
||||
public abstract byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException;
|
||||
|
||||
|
||||
protected short convertUnsigned(byte x) {
|
||||
short ss = x;
|
||||
|
||||
if (ss < 0) {
|
||||
ss += 256;
|
||||
}
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
|
||||
/* O(n) lookup. Run on an O(n) translation of a byte-stream, gives O(n**2) performance. Sigh. */
|
||||
public static int encode4b6bListIndex(byte b) {
|
||||
for (int i = 0; i < encode4b6bList.length; i++) {
|
||||
if (b == encode4b6bList[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public void writeError(Logger LOG, byte[] raw, String errorData) {
|
||||
|
||||
LOG.error("\n=============================================================================\n" + //
|
||||
" Decoded payload length is zero.\n" +
|
||||
" encodedPayload: {}\n" +
|
||||
" errors: {}\n" +
|
||||
"=============================================================================", //
|
||||
ByteUtil.getHex(raw), errorData);
|
||||
|
||||
//FabricUtil.createEvent("MedtronicDecode4b6bError", null);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public class Encoding4b6bGeoff extends Encoding4b6bAbstract {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGeoff.class);
|
||||
|
||||
|
||||
public byte[] encode4b6b(byte[] data) {
|
||||
// if ((data.length % 2) != 0) {
|
||||
// LOG.error("Warning: data is odd number of bytes");
|
||||
// }
|
||||
// use arraylists because byte[] is annoying.
|
||||
List<Byte> inData = ByteUtil.getListFromByteArray(data);
|
||||
List<Byte> outData = new ArrayList<>();
|
||||
|
||||
int acc = 0;
|
||||
int bitcount = 0;
|
||||
int i;
|
||||
for (i = 0; i < inData.size(); i++) {
|
||||
acc <<= 6;
|
||||
acc |= encode4b6bList[(inData.get(i) >> 4) & 0x0f];
|
||||
bitcount += 6;
|
||||
|
||||
acc <<= 6;
|
||||
acc |= encode4b6bList[inData.get(i) & 0x0f];
|
||||
bitcount += 6;
|
||||
|
||||
while (bitcount >= 8) {
|
||||
byte outByte = (byte) (acc >> (bitcount - 8) & 0xff);
|
||||
outData.add(outByte);
|
||||
bitcount -= 8;
|
||||
acc &= (0xffff >> (16 - bitcount));
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
acc <<= 6;
|
||||
acc |= 0x14; // marks uneven packet boundary.
|
||||
bitcount += 6;
|
||||
if (bitcount >= 8) {
|
||||
byte outByte = (byte) ((acc >> (bitcount - 8)) & 0xff);
|
||||
outData.add(outByte);
|
||||
bitcount -= 8;
|
||||
// acc &= (0xffff >> (16 - bitcount));
|
||||
}
|
||||
while (bitcount >= 8) {
|
||||
outData.add((byte) 0);
|
||||
bitcount -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
// convert back to byte[]
|
||||
byte[] rval = ByteUtil.getByteArrayFromList(outData);
|
||||
|
||||
return rval;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode by Geoff
|
||||
*
|
||||
* @param raw
|
||||
* @return
|
||||
* @throws NumberFormatException
|
||||
*/
|
||||
public byte[] decode4b6b(byte[] raw) throws RileyLinkCommunicationException {
|
||||
|
||||
StringBuilder errorMessageBuilder = new StringBuilder();
|
||||
|
||||
errorMessageBuilder.append("Input data: " + ByteUtil.shortHexString(raw) + "\n");
|
||||
|
||||
if ((raw.length % 2) != 0) {
|
||||
errorMessageBuilder.append("Warn: odd number of bytes.\n");
|
||||
}
|
||||
|
||||
byte[] rval = new byte[]{};
|
||||
int availableBits = 0;
|
||||
int codingErrors = 0;
|
||||
int x = 0;
|
||||
// Log.w(TAG,"decode4b6b: untested code");
|
||||
// Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw)));
|
||||
for (int i = 0; i < raw.length; i++) {
|
||||
int unsignedValue = raw[i];
|
||||
if (unsignedValue < 0) {
|
||||
unsignedValue += 256;
|
||||
}
|
||||
x = (x << 8) + unsignedValue;
|
||||
availableBits += 8;
|
||||
if (availableBits >= 12) {
|
||||
// take top six
|
||||
int highcode = (x >> (availableBits - 6)) & 0x3F;
|
||||
int highIndex = encode4b6bListIndex((byte) (highcode));
|
||||
// take bottom six
|
||||
int lowcode = (x >> (availableBits - 12)) & 0x3F;
|
||||
int lowIndex = encode4b6bListIndex((byte) (lowcode));
|
||||
// special case at end of transmission on uneven boundaries:
|
||||
if ((highIndex >= 0) && (lowIndex >= 0)) {
|
||||
byte decoded = (byte) ((highIndex << 4) + lowIndex);
|
||||
rval = ByteUtil.concat(rval, decoded);
|
||||
/*
|
||||
* LOG.debug(String.format(
|
||||
* "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s"
|
||||
* ,
|
||||
* i,x,highcode,highIndex, lowcode,
|
||||
* lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString
|
||||
* (ByteUtil.substring(raw,i+1,raw.length-i-1))));
|
||||
*/
|
||||
} else {
|
||||
// LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits));
|
||||
errorMessageBuilder.append(String.format(
|
||||
"decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n",
|
||||
i, x, highcode, lowcode, availableBits));
|
||||
codingErrors++;
|
||||
}
|
||||
|
||||
availableBits -= 12;
|
||||
x = x & (0x0000ffff >> (16 - availableBits));
|
||||
}
|
||||
}
|
||||
|
||||
if (availableBits != 0) {
|
||||
if ((availableBits == 4) && (x == 0x05)) {
|
||||
// normal end
|
||||
} else {
|
||||
// LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits +
|
||||
// ")");
|
||||
errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)("
|
||||
+ availableBits + ")\n");
|
||||
codingErrors++;
|
||||
}
|
||||
} else {
|
||||
// also normal end.
|
||||
}
|
||||
|
||||
if (codingErrors > 0) {
|
||||
// LOG.error("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||
errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||
writeError(LOG, raw, errorMessageBuilder.toString());
|
||||
throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString());
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
// public static RFTools.DecodeResponseDto decode4b6bWithoutException(byte[] raw) {
|
||||
// /*
|
||||
// * if ((raw.length % 2) != 0) {
|
||||
// * LOG.error("Warning: data is odd number of bytes");
|
||||
// * }
|
||||
// */
|
||||
//
|
||||
// RFTools.DecodeResponseDto response = new RFTools.DecodeResponseDto();
|
||||
//
|
||||
// StringBuilder errorMessageBuilder = new StringBuilder();
|
||||
//
|
||||
// errorMessageBuilder.append("Input data: " + ByteUtil.getHex(raw) + "\n");
|
||||
//
|
||||
// if ((raw.length % 2) != 0) {
|
||||
// errorMessageBuilder.append("Warn: odd number of bytes.");
|
||||
// }
|
||||
//
|
||||
// byte[] rval = new byte[] {};
|
||||
// int availableBits = 0;
|
||||
// int codingErrors = 0;
|
||||
// int x = 0;
|
||||
// // Log.w(TAG,"decode4b6b: untested code");
|
||||
// // Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw)));
|
||||
// for (int i = 0; i < raw.length; i++) {
|
||||
// int unsignedValue = raw[i];
|
||||
// if (unsignedValue < 0) {
|
||||
// unsignedValue += 256;
|
||||
// }
|
||||
// x = (x << 8) + unsignedValue;
|
||||
// availableBits += 8;
|
||||
// if (availableBits >= 12) {
|
||||
// // take top six
|
||||
// int highcode = (x >> (availableBits - 6)) & 0x3F;
|
||||
// int highIndex = encode4b6bListIndex((byte)(highcode));
|
||||
// // take bottom six
|
||||
// int lowcode = (x >> (availableBits - 12)) & 0x3F;
|
||||
// int lowIndex = encode4b6bListIndex((byte)(lowcode));
|
||||
// // special case at end of transmission on uneven boundaries:
|
||||
// if ((highIndex >= 0) && (lowIndex >= 0)) {
|
||||
// byte decoded = (byte)((highIndex << 4) + lowIndex);
|
||||
// rval = ByteUtil.concat(rval, decoded);
|
||||
// /*
|
||||
// * LOG.debug(String.format(
|
||||
// *
|
||||
// "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s"
|
||||
// * ,
|
||||
// * i,x,highcode,highIndex, lowcode,
|
||||
// * lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString
|
||||
// * (ByteUtil.substring(raw,i+1,raw.length-i-1))));
|
||||
// */
|
||||
// } else {
|
||||
// //
|
||||
// LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits));
|
||||
// errorMessageBuilder.append(String.format(
|
||||
// "decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n",
|
||||
// i, x, highcode, lowcode, availableBits));
|
||||
// codingErrors++;
|
||||
// }
|
||||
//
|
||||
// availableBits -= 12;
|
||||
// x = x & (0x0000ffff >> (16 - availableBits));
|
||||
// } else {
|
||||
// // LOG.debug(String.format("i=%d, skip: x=0x%08X, available bits %d",i,x,availableBits));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (availableBits != 0) {
|
||||
// if ((availableBits == 4) && (x == 0x05)) {
|
||||
// // normal end
|
||||
// } else {
|
||||
// LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + ")");
|
||||
// errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)("
|
||||
// + availableBits + ")\n");
|
||||
// codingErrors++;
|
||||
// }
|
||||
// } else {
|
||||
// // also normal end.
|
||||
// }
|
||||
//
|
||||
// if (codingErrors > 0) {
|
||||
// LOG.error("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||
// errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||
//
|
||||
// response.errorData = errorMessageBuilder.toString();
|
||||
// } else {
|
||||
// response.data = rval;
|
||||
// }
|
||||
//
|
||||
// return response;
|
||||
// }
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public class Encoding4b6bGo extends Encoding4b6bAbstract {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGo.class);
|
||||
private static Map<Short, Short> decodeGoMap;
|
||||
|
||||
|
||||
public byte[] encode4b6b(byte[] src) {
|
||||
// 2 input bytes produce 3 output bytes.
|
||||
// Odd final input byte, if any, produces 2 output bytes.
|
||||
int n = src.length;
|
||||
byte[] dst = new byte[3 * (n / 2) + 2 * (n % 2)];
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < n; i += 2, j = j + 3) {
|
||||
short x = convertUnsigned(src[i]);
|
||||
short a = encode4b6bList[hi(4, x)];
|
||||
short b = encode4b6bList[lo(4, x)];
|
||||
dst[j] = (byte)(a << 2 | hi(4, b));
|
||||
if (i + 1 < n) {
|
||||
short y = convertUnsigned(src[i + 1]);
|
||||
short c = encode4b6bList[hi(4, y)];
|
||||
short d = encode4b6bList[lo(4, y)];
|
||||
dst[j + 1] = (byte)(lo(4, b) << 4 | hi(6, c));
|
||||
dst[j + 2] = (byte)(lo(2, c) << 6 | d);
|
||||
} else {
|
||||
// Fill final nibble with 5 to match pump behavior.
|
||||
dst[j + 1] = (byte)(lo(4, b) << 4 | 0x5);
|
||||
}
|
||||
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode from Go code by ecc1. NOT WORKING
|
||||
*
|
||||
* @param src
|
||||
* @return
|
||||
*/
|
||||
public byte[] decode4b6b(byte[] src) throws RileyLinkCommunicationException {
|
||||
int n = src.length;
|
||||
|
||||
if (decodeGoMap == null)
|
||||
initDecodeGo();
|
||||
|
||||
StringBuilder errorMessageBuilder = new StringBuilder();
|
||||
|
||||
errorMessageBuilder.append("Input data: " + ByteUtil.getHex(src) + "\n");
|
||||
int codingErrors = 0;
|
||||
|
||||
// Check for valid packet length.
|
||||
if (n % 3 == 1) {
|
||||
errorMessageBuilder.append("Invalid package length " + n);
|
||||
codingErrors++;
|
||||
// return nil, ErrDecoding
|
||||
}
|
||||
// 3 input bytes produce 2 output bytes.
|
||||
// Final 2 input bytes, if any, produce 1 output byte.
|
||||
byte[] dst = new byte[2 * (n / 3) + (n % 3) / 2];
|
||||
|
||||
int j = 0;
|
||||
for (int i = 0; i < n; i = i + 3, j = j + 2) {
|
||||
if (i + 1 >= n) {
|
||||
errorMessageBuilder.append("Overflow in i (" + i + ")");
|
||||
}
|
||||
short x = convertUnsigned(src[i]);
|
||||
short y = convertUnsigned(src[i + 1]);
|
||||
short a = decode6b_goMap(hi(6, x));
|
||||
short b = decode6b_goMap(lo(2, x) << 4 | hi(4, y));
|
||||
if (a == 0xFF || b == 0xFF) {
|
||||
errorMessageBuilder.append("Error decoding ");
|
||||
codingErrors++;
|
||||
}
|
||||
dst[j] = (byte)(a << 4 | b);
|
||||
if (i + 2 < n) {
|
||||
short z = convertUnsigned(src[i + 2]);
|
||||
short c = decode6b_goMap(lo(4, y) << 2 | hi(2, z));
|
||||
short d = decode6b_goMap(lo(6, z));
|
||||
if (c == 0xFF || d == 0xFF) {
|
||||
errorMessageBuilder.append("Error decoding ");
|
||||
codingErrors++;
|
||||
}
|
||||
dst[j + 1] = (byte)(c << 4 | d);
|
||||
}
|
||||
}
|
||||
|
||||
if (codingErrors > 0) {
|
||||
errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered.");
|
||||
writeError(LOG, dst, errorMessageBuilder.toString());
|
||||
throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString());
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
static short hi(int n, short x) {
|
||||
// x = convertUnsigned(x);
|
||||
return (short)(x >> (8 - n));
|
||||
}
|
||||
|
||||
|
||||
static short lo(int n, short x) {
|
||||
// byte b = (byte)x;
|
||||
return (short)(x & ((1 << n) - 1));
|
||||
}
|
||||
|
||||
|
||||
public static void initDecodeGo() {
|
||||
|
||||
decodeGoMap = new HashMap<>();
|
||||
|
||||
putToMap(0x0B, 0x0B);
|
||||
putToMap(0x0D, 0x0D);
|
||||
putToMap(0x0E, 0x0E);
|
||||
putToMap(0x15, 0x00);
|
||||
putToMap(0x16, 0x07);
|
||||
putToMap(0x19, 0x09);
|
||||
putToMap(0x1A, 0x08);
|
||||
putToMap(0x1C, 0x0F);
|
||||
putToMap(0x23, 0x03);
|
||||
putToMap(0x25, 0x05);
|
||||
putToMap(0x26, 0x06);
|
||||
putToMap(0x2A, 0x0A);
|
||||
putToMap(0x2C, 0x0C);
|
||||
putToMap(0x31, 0x01);
|
||||
putToMap(0x32, 0x02);
|
||||
putToMap(0x34, 0x04);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static short decode6b_goMap(int value) {
|
||||
short val = (short)value;
|
||||
if (decodeGoMap.containsKey(val))
|
||||
return decodeGoMap.get(val);
|
||||
else
|
||||
return (short)0xff;
|
||||
}
|
||||
|
||||
|
||||
private static void putToMap(int val1, int val2) {
|
||||
decodeGoMap.put((short)val1, (short)val2);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public class Encoding4b6bLoop extends Encoding4b6bAbstract {
|
||||
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bLoop.class);
|
||||
public Map<Integer, Byte> codesRev = null;
|
||||
|
||||
|
||||
public Encoding4b6bLoop() {
|
||||
createCodeRev();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is almost 1:1 with same method from Loop, only change is unsigning of element and |05 added for
|
||||
* last byte. It should work better than original one, which is really different than this one.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public byte[] encode4b6b(byte[] data) {
|
||||
|
||||
List<Byte> buffer = new ArrayList<Byte>();
|
||||
int bitAccumulator = 0x0;
|
||||
int bitcount = 0;
|
||||
|
||||
for (byte element : data) {
|
||||
|
||||
short element2 = element;
|
||||
|
||||
if (element2 < 0) {
|
||||
element2 += 256;
|
||||
}
|
||||
|
||||
bitAccumulator <<= 6;
|
||||
bitAccumulator |= encode4b6bList[element2 >> 4];
|
||||
bitcount += 6;
|
||||
|
||||
bitAccumulator <<= 6;
|
||||
bitAccumulator |= encode4b6bList[element2 & 0x0f];
|
||||
bitcount += 6;
|
||||
|
||||
while (bitcount >= 8) {
|
||||
buffer.add((byte)((bitAccumulator >> (bitcount - 8)) & 0xff));
|
||||
bitcount -= 8;
|
||||
bitAccumulator &= (0xffff >> (16 - bitcount));
|
||||
}
|
||||
}
|
||||
|
||||
if (bitcount > 0) {
|
||||
bitAccumulator <<= (8 - bitcount);
|
||||
buffer.add((byte)((bitAccumulator | 0x5) & 0xff));
|
||||
}
|
||||
|
||||
return ByteUtil.getByteArrayFromList(buffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DOESN'T WORK YET
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws RileyLinkCommunicationException
|
||||
*/
|
||||
public byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException {
|
||||
List<Byte> buffer = new ArrayList<Byte>();
|
||||
int availBits = 0;
|
||||
int bitAccumulator = 0;
|
||||
|
||||
for (byte element2 : data) {
|
||||
|
||||
short element = convertUnsigned(element2);
|
||||
|
||||
// if (element < 0) {
|
||||
// element += 255;
|
||||
// }
|
||||
|
||||
if (element == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bitAccumulator = (bitAccumulator << 8) + element;
|
||||
availBits += 8;
|
||||
|
||||
if (availBits >= 12) {
|
||||
|
||||
int hiNibble;
|
||||
int loNibble;
|
||||
|
||||
try {
|
||||
int index = (bitAccumulator >> (availBits - 6));
|
||||
int index2 = ((bitAccumulator >> (availBits - 12)) & 0b111111);
|
||||
hiNibble = codesRev.get((bitAccumulator >> (availBits - 6)));
|
||||
loNibble = codesRev.get(((bitAccumulator >> (availBits - 12)) & 0b111111));
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Exception: " + ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
int decoded = ((hiNibble << 4) + loNibble);
|
||||
buffer.add((byte)decoded);
|
||||
availBits -= 12;
|
||||
bitAccumulator = bitAccumulator & (0xffff >> (16 - availBits));
|
||||
}
|
||||
}
|
||||
|
||||
return ByteUtil.getByteArrayFromList(buffer);
|
||||
}
|
||||
|
||||
|
||||
private void createCodeRev() {
|
||||
codesRev = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < encode4b6bList.length; i++) {
|
||||
codesRev.put(i, encode4b6bList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 21/05/2018.
|
||||
*/
|
||||
|
||||
public enum CC111XRegister {
|
||||
|
||||
sync1(0x00), //
|
||||
sync0(0x01), //
|
||||
pktlen(0x02), //
|
||||
pktctrl1(0x03), //
|
||||
pktctrl0(0x04), //
|
||||
fsctrl1(0x07), //
|
||||
freq2(0x09), //
|
||||
freq1(0x0a), //
|
||||
freq0(0x0b), //
|
||||
mdmcfg4(0x0c), //
|
||||
mdmcfg3(0x0d), //
|
||||
mdmcfg2(0x0e), //
|
||||
mdmcfg1(0x0f), //
|
||||
mdmcfg0(0x10), //
|
||||
deviatn(0x11), //
|
||||
mcsm0(0x14), //
|
||||
foccfg(0x15), //
|
||||
agcctrl2(0x17), //
|
||||
agcctrl1(0x18), //
|
||||
agcctrl0(0x19), //
|
||||
frend1(0x1a), //
|
||||
frend0(0x1b), //
|
||||
fscal3(0x1c), //
|
||||
fscal2(0x1d), //
|
||||
fscal1(0x1e), //
|
||||
fscal0(0x1f), //
|
||||
test1(0x24), //
|
||||
test0(0x25), //
|
||||
paTable0(0x2e), //
|
||||
|
||||
;
|
||||
|
||||
public byte value;
|
||||
|
||||
|
||||
CC111XRegister(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 22/05/2018.
|
||||
*/
|
||||
|
||||
public enum RFSpyCommand {
|
||||
|
||||
GetState(1), //
|
||||
GetVersion(2, false), //
|
||||
GetPacket(3), // aka Listen, receive
|
||||
Send(4), //
|
||||
SendAndListen(5), //
|
||||
UpdateRegister(6), //
|
||||
Reset(7), //
|
||||
|
||||
;
|
||||
|
||||
public byte code;
|
||||
private boolean encoded = true;
|
||||
|
||||
|
||||
RFSpyCommand(int code) {
|
||||
this.code = (byte)code;
|
||||
}
|
||||
|
||||
|
||||
RFSpyCommand(int code, boolean encoded) {
|
||||
this.code = (byte)code;
|
||||
this.encoded = encoded;
|
||||
}
|
||||
|
||||
|
||||
public boolean isEncoded() {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
|
||||
public void setEncoded(boolean encoded) {
|
||||
this.encoded = encoded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
public enum RFSpyRLResponse {
|
||||
// 0xaa == timeout
|
||||
// 0xbb == interrupted
|
||||
// 0xcc == zero-data
|
||||
// 0xdd == success
|
||||
// 0x11 == invalidParam
|
||||
// 0x22 == unknownCommand
|
||||
|
||||
Invalid(0), // default, just fail
|
||||
Timeout(0xAA),
|
||||
Interrupted(0xBB),
|
||||
ZeroData(0xCC),
|
||||
Success(0xDD),
|
||||
OldSuccess(0x01),
|
||||
InvalidParam(0x11),
|
||||
UnknownCommand(0x22), ;
|
||||
|
||||
byte value;
|
||||
|
||||
|
||||
RFSpyRLResponse(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
|
||||
|
||||
public static RFSpyRLResponse fromByte(byte input) {
|
||||
for (RFSpyRLResponse type : values()) {
|
||||
if (type.value == input) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/6/18.
|
||||
*/
|
||||
|
||||
public enum RLMessageType {
|
||||
PowerOn, // for powering on the pump (wakeup)
|
||||
ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel)
|
||||
;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 21/05/2018.
|
||||
*/
|
||||
|
||||
public enum RXFilterMode {
|
||||
|
||||
Wide(0x50), //
|
||||
Narrow(0x90) //
|
||||
;
|
||||
|
||||
public byte value;
|
||||
|
||||
|
||||
RXFilterMode(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 11/24/18.
|
||||
*/
|
||||
|
||||
public enum RileyLinkBLEError {
|
||||
CodingErrors("Coding Errors encountered during decode of RileyLink packet."), //
|
||||
Timeout("Timeout"), //
|
||||
Interrupted("Interrupted"),
|
||||
TooShortOrNullResponse("Too short or null decoded response.");
|
||||
|
||||
private String description;
|
||||
|
||||
|
||||
RileyLinkBLEError(String description) {
|
||||
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 22/05/2018.
|
||||
*/
|
||||
|
||||
public enum RileyLinkCommandType {
|
||||
|
||||
GetState(1), //
|
||||
GetVersion(2), //
|
||||
GetPacket(3), // aka Listen, receive
|
||||
Send(4), //
|
||||
SendAndListen(5), //
|
||||
UpdateRegister(6), //
|
||||
Reset(7), //
|
||||
Led(8),
|
||||
ReadRegister(9),
|
||||
SetModeRegisters(10),
|
||||
SetHardwareEncoding(11),
|
||||
SetPreamble(12),
|
||||
ResetRadioConfig(13),
|
||||
GetStatistics(14), ;
|
||||
|
||||
public byte code;
|
||||
|
||||
|
||||
RileyLinkCommandType(int code) {
|
||||
this.code = (byte)code;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
|
||||
public enum RileyLinkEncodingType {
|
||||
|
||||
None(0x00, null), // No encoding on RL
|
||||
Manchester(0x01, null), // Manchester encoding on RL (for Omnipod)
|
||||
FourByteSixByteRileyLink(0x02, R.string.medtronic_pump_encoding_4b6b_rileylink), // 4b6b encoding on RL (for Medtronic)
|
||||
FourByteSixByteLocal(0x00, R.string.medtronic_pump_encoding_4b6b_local), // No encoding on RL, but 4b6b encoding in code
|
||||
;
|
||||
|
||||
public byte value;
|
||||
public Integer resourceId;
|
||||
public String description;
|
||||
|
||||
private static Map<String, RileyLinkEncodingType> encodingTypeMap;
|
||||
|
||||
static {
|
||||
encodingTypeMap = new HashMap<>();
|
||||
|
||||
for (RileyLinkEncodingType encType : values()) {
|
||||
if (encType.resourceId!=null) {
|
||||
encodingTypeMap.put(MainApp.gs(encType.resourceId), encType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RileyLinkEncodingType(int value) {
|
||||
this.value = (byte)value;
|
||||
}
|
||||
|
||||
|
||||
RileyLinkEncodingType(int value, Integer resourceId) {
|
||||
this.value = (byte)value;
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public static RileyLinkEncodingType getByDescription(String description) {
|
||||
if (encodingTypeMap.containsKey(description)) {
|
||||
return encodingTypeMap.get(description);
|
||||
}
|
||||
|
||||
return RileyLinkEncodingType.FourByteSixByteLocal;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public enum RileyLinkFirmwareVersion {
|
||||
|
||||
Version_0_0(0, 0, "0.0"), // just for defaulting
|
||||
Version_0_9(0, 9, "0.9"), //
|
||||
Version_1_0(1, 0, "1.0"), //
|
||||
Version_2_0(2, 0, "2.0"), //
|
||||
Version_2_2(2, 2, "2.2"), //
|
||||
Version_3_0(3, 0, "3.0"), //
|
||||
UnknownVersion(0, 0, "???"), //
|
||||
Version1(Version_0_0, Version_0_9, Version_1_0), //
|
||||
Version2(Version_2_0, Version_2_2), //
|
||||
Version2AndHigher(Version_2_0, Version_2_2, Version_3_0), //
|
||||
;
|
||||
|
||||
private static final String FIRMWARE_IDENTIFICATION_PREFIX = "subg_rfspy ";
|
||||
private static final Pattern _version_pattern = Pattern.compile(FIRMWARE_IDENTIFICATION_PREFIX
|
||||
+ "([0-9]+)\\.([0-9]+)");
|
||||
static Map<String, RileyLinkFirmwareVersion> mapByVersion;
|
||||
|
||||
static {
|
||||
mapByVersion = new HashMap<>();
|
||||
for (RileyLinkFirmwareVersion version : values()) {
|
||||
if (version.familyMembers == null) {
|
||||
mapByVersion.put(version.versionKey, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected RileyLinkFirmwareVersion[] familyMembers;
|
||||
private int major;
|
||||
private int minor;
|
||||
private String versionKey = "";
|
||||
|
||||
|
||||
RileyLinkFirmwareVersion(int major, int minor, String versionKey) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.versionKey = versionKey;
|
||||
}
|
||||
|
||||
|
||||
RileyLinkFirmwareVersion(RileyLinkFirmwareVersion... familyMembers) {
|
||||
this.familyMembers = familyMembers;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isSameVersion(RileyLinkFirmwareVersion versionWeCheck, RileyLinkFirmwareVersion versionSources) {
|
||||
if (versionSources.familyMembers != null) {
|
||||
for (RileyLinkFirmwareVersion vrs : versionSources.familyMembers) {
|
||||
if (vrs == versionWeCheck)
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return (versionWeCheck == versionSources);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static RileyLinkFirmwareVersion getByVersionString(String versionString) {
|
||||
if (versionString != null) {
|
||||
Matcher m = _version_pattern.matcher(versionString);
|
||||
if (m.find()) {
|
||||
int major = Integer.parseInt(m.group(1));
|
||||
int minor = Integer.parseInt(m.group(2));
|
||||
String versionKey = major + "." + minor;
|
||||
if (mapByVersion.containsKey(versionKey)) {
|
||||
return mapByVersion.get(versionKey);
|
||||
} else {
|
||||
return defaultToLowestMajorVersion(major); // just in case there is new release that we don't cover
|
||||
// example: 2.3 etc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RileyLinkFirmwareVersion.UnknownVersion;
|
||||
}
|
||||
|
||||
|
||||
private static RileyLinkFirmwareVersion defaultToLowestMajorVersion(int major) {
|
||||
if (mapByVersion.containsKey(major + ".0")) {
|
||||
return mapByVersion.get(major + ".0");
|
||||
}
|
||||
return UnknownVersion;
|
||||
}
|
||||
|
||||
|
||||
public boolean isSameVersion(RileyLinkFirmwareVersion versionSources) {
|
||||
return isSameVersion(this, versionSources);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return FIRMWARE_IDENTIFICATION_PREFIX + versionKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 6/7/18.
|
||||
*/
|
||||
|
||||
public enum RileyLinkTargetFrequency {
|
||||
|
||||
Medtronic_WorldWide(868.25, 868.3, 868.35, 868.4, 868.45, 868.5, 868.55, 868.6, 868.65), //
|
||||
Medtronic_US(916.45, 916.5, 916.55, 916.6, 916.65, 916.7, 916.75, 916.8), //
|
||||
Omnipod(433.91), //
|
||||
;
|
||||
|
||||
double[] frequencies;
|
||||
|
||||
|
||||
RileyLinkTargetFrequency(double... frequencies) {
|
||||
this.frequencies = frequencies;
|
||||
}
|
||||
|
||||
|
||||
public double[] getScanFrequencies() {
|
||||
return frequencies;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public abstract class BLECommOperation {
|
||||
|
||||
public boolean timedOut = false;
|
||||
public boolean interrupted = false;
|
||||
protected byte[] value;
|
||||
protected BluetoothGatt gatt;
|
||||
protected Semaphore operationComplete = new Semaphore(0, true);
|
||||
|
||||
|
||||
// This is to be run on the main thread
|
||||
public abstract void execute(RileyLinkBLE comm);
|
||||
|
||||
|
||||
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||
}
|
||||
|
||||
|
||||
public int getGattOperationTimeout_ms() {
|
||||
return 22000;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class BLECommOperationResult {
|
||||
|
||||
public static final int RESULT_NONE = 0;
|
||||
public static final int RESULT_SUCCESS = 1;
|
||||
public static final int RESULT_TIMEOUT = 2;
|
||||
public static final int RESULT_BUSY = 3;
|
||||
public static final int RESULT_INTERRUPTED = 4;
|
||||
public static final int RESULT_NOT_CONFIGURED = 5;
|
||||
public byte[] value;
|
||||
public int resultCode;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class CharacteristicReadOperation extends BLECommOperation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
|
||||
private BluetoothGattCharacteristic characteristic;
|
||||
|
||||
|
||||
public CharacteristicReadOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara) {
|
||||
this.gatt = gatt;
|
||||
this.characteristic = chara;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(RileyLinkBLE comm) {
|
||||
gatt.readCharacteristic(characteristic);
|
||||
// wait here for callback to notify us that value was read.
|
||||
try {
|
||||
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||
if (didAcquire) {
|
||||
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||
// understanding of the sequence of events.
|
||||
// success
|
||||
} else {
|
||||
LOG.error("Timeout waiting for gatt write operation to complete");
|
||||
timedOut = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (isLogEnabled())
|
||||
LOG.error("Interrupted while waiting for gatt write operation to complete");
|
||||
interrupted = true;
|
||||
}
|
||||
value = characteristic.getValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||
super.gattOperationCompletionCallback(uuid, value);
|
||||
if (!characteristic.getUuid().equals(uuid)) {
|
||||
LOG.error(String.format(
|
||||
"Completion callback: UUID does not match! out of sequence? Found: %s, should be %s",
|
||||
GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid)));
|
||||
}
|
||||
operationComplete.release();
|
||||
}
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPBTCOMM);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class CharacteristicWriteOperation extends BLECommOperation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
|
||||
|
||||
private BluetoothGattCharacteristic characteristic;
|
||||
|
||||
|
||||
public CharacteristicWriteOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara, byte[] value) {
|
||||
this.gatt = gatt;
|
||||
this.characteristic = chara;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(RileyLinkBLE comm) {
|
||||
|
||||
characteristic.setValue(value);
|
||||
gatt.writeCharacteristic(characteristic);
|
||||
// wait here for callback to notify us that value was written.
|
||||
try {
|
||||
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||
if (didAcquire) {
|
||||
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||
// understanding of the sequence of events.
|
||||
// success
|
||||
} else {
|
||||
LOG.error("Timeout waiting for gatt write operation to complete");
|
||||
timedOut = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Interrupted while waiting for gatt write operation to complete");
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// This will be run on the IBinder thread
|
||||
@Override
|
||||
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||
if (!characteristic.getUuid().equals(uuid)) {
|
||||
LOG.error(String.format(
|
||||
"Completion callback: UUID does not match! out of sequence? Found: %s, should be %s",
|
||||
GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid)));
|
||||
}
|
||||
operationComplete.release();
|
||||
}
|
||||
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPBTCOMM);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
|
||||
/**
|
||||
* Created by geoff on 5/26/16.
|
||||
*/
|
||||
public class DescriptorWriteOperation extends BLECommOperation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DescriptorWriteOperation.class);
|
||||
|
||||
private BluetoothGattDescriptor descr;
|
||||
|
||||
|
||||
public DescriptorWriteOperation(BluetoothGatt gatt, BluetoothGattDescriptor descr, byte[] value) {
|
||||
this.gatt = gatt;
|
||||
this.descr = descr;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void gattOperationCompletionCallback(UUID uuid, byte[] value) {
|
||||
super.gattOperationCompletionCallback(uuid, value);
|
||||
operationComplete.release();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(RileyLinkBLE comm) {
|
||||
descr.setValue(value);
|
||||
gatt.writeDescriptor(descr);
|
||||
// wait here for callback to notify us that value was read.
|
||||
try {
|
||||
boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS);
|
||||
if (didAcquire) {
|
||||
SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier
|
||||
// understanding of the sequence of events.
|
||||
// success
|
||||
} else {
|
||||
LOG.error("Timeout waiting for descriptor write operation to complete");
|
||||
timedOut = true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Interrupted while waiting for descriptor write operation to complete");
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by andy on 9/10/18.
|
||||
*/
|
||||
|
||||
public class BleAdvertisedData {
|
||||
|
||||
private List<UUID> mUuids;
|
||||
private String mName;
|
||||
|
||||
|
||||
public BleAdvertisedData(List<UUID> uuids, String name) {
|
||||
mUuids = uuids;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
|
||||
public List<UUID> getUuids() {
|
||||
return mUuids;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "BleAdvertisedData [name=" + mName + ", UUIDs=" + mUuids + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||
|
||||
/**
|
||||
* Created by andy on 4/5/19.
|
||||
*/
|
||||
|
||||
public class CommandValueDefinition {
|
||||
|
||||
public CommandValueDefinitionType definitionType;
|
||||
public String value;
|
||||
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
public class RLHistoryItem {
|
||||
|
||||
private MedtronicCommandType medtronicCommandType;
|
||||
private LocalDateTime dateTime;
|
||||
private RLHistoryItemSource source;
|
||||
private RileyLinkServiceState serviceState;
|
||||
private RileyLinkError errorCode;
|
||||
|
||||
private RileyLinkTargetDevice targetDevice;
|
||||
private PumpDeviceState pumpDeviceState;
|
||||
|
||||
|
||||
public RLHistoryItem(RileyLinkServiceState serviceState, RileyLinkError errorCode,
|
||||
RileyLinkTargetDevice targetDevice) {
|
||||
this.targetDevice = targetDevice;
|
||||
this.dateTime = new LocalDateTime();
|
||||
this.serviceState = serviceState;
|
||||
this.errorCode = errorCode;
|
||||
this.source = RLHistoryItemSource.RileyLink;
|
||||
}
|
||||
|
||||
|
||||
public RLHistoryItem(PumpDeviceState pumpDeviceState, RileyLinkTargetDevice targetDevice) {
|
||||
this.pumpDeviceState = pumpDeviceState;
|
||||
this.dateTime = new LocalDateTime();
|
||||
this.targetDevice = targetDevice;
|
||||
this.source = RLHistoryItemSource.MedtronicPump;
|
||||
}
|
||||
|
||||
|
||||
public RLHistoryItem(MedtronicCommandType medtronicCommandType) {
|
||||
this.dateTime = new LocalDateTime();
|
||||
this.medtronicCommandType = medtronicCommandType;
|
||||
source = RLHistoryItemSource.MedtronicCommand;
|
||||
}
|
||||
|
||||
|
||||
public LocalDateTime getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
|
||||
public RileyLinkServiceState getServiceState() {
|
||||
return serviceState;
|
||||
}
|
||||
|
||||
|
||||
public RileyLinkError getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
// TODO extend when we have Omnipod
|
||||
switch (this.source) {
|
||||
case RileyLink:
|
||||
return "State: " + MainApp.gs(serviceState.getResourceId(targetDevice))
|
||||
+ (this.errorCode == null ? "" : ", Error Code: " + errorCode);
|
||||
|
||||
case MedtronicPump:
|
||||
return MainApp.gs(pumpDeviceState.getResourceId());
|
||||
|
||||
case MedtronicCommand:
|
||||
return medtronicCommandType.name();
|
||||
|
||||
default:
|
||||
return "Unknown Description";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public RLHistoryItemSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
public PumpDeviceState getPumpDeviceState() {
|
||||
return pumpDeviceState;
|
||||
}
|
||||
|
||||
public enum RLHistoryItemSource {
|
||||
RileyLink("RileyLink"), //
|
||||
MedtronicPump("Medtronic"), //
|
||||
MedtronicCommand("Medtronic");
|
||||
|
||||
private String desc;
|
||||
|
||||
|
||||
RLHistoryItemSource(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 4/5/19.
|
||||
*/
|
||||
|
||||
public enum CommandValueDefinitionRLType implements CommandValueDefinitionType {
|
||||
Name, //
|
||||
Firmware, //
|
||||
SignalStrength, //
|
||||
ConnectionState, //
|
||||
Frequency, //
|
||||
;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String commandAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||
|
||||
/**
|
||||
* Created by andy on 4/5/19.
|
||||
*/
|
||||
|
||||
public interface CommandValueDefinitionType {
|
||||
|
||||
String getName();
|
||||
|
||||
|
||||
String getDescription();
|
||||
|
||||
|
||||
String commandAction();
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
|
||||
/**
|
||||
* Created by andy on 14/05/2018.
|
||||
*/
|
||||
|
||||
public enum RileyLinkError {
|
||||
|
||||
// Configuration
|
||||
|
||||
// BT
|
||||
NoBluetoothAdapter(R.string.rileylink_error_no_bt_adapter), //
|
||||
BluetoothDisabled(R.string.rileylink_error_bt_disabled), //
|
||||
|
||||
// RileyLink
|
||||
RileyLinkUnreachable(R.string.rileylink_error_unreachable), //
|
||||
DeviceIsNotRileyLink(R.string.rileylink_error_not_rl), //
|
||||
|
||||
// Device
|
||||
TuneUpOfDeviceFailed(R.string.rileylink_error_tuneup_failed), //
|
||||
NoContactWithDevice(R.string.rileylink_error_pump_unreachable, R.string.rileylink_error_pod_unreachable), //
|
||||
;
|
||||
|
||||
int resourceId;
|
||||
Integer resourceIdPod;
|
||||
|
||||
|
||||
RileyLinkError(int resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
|
||||
RileyLinkError(int resourceId, int resourceIdPod) {
|
||||
this.resourceId = resourceId;
|
||||
this.resourceIdPod = resourceIdPod;
|
||||
}
|
||||
|
||||
|
||||
public int getResourceId(RileyLinkTargetDevice targetDevice) {
|
||||
if (this.resourceIdPod != null) {
|
||||
|
||||
return targetDevice == RileyLinkTargetDevice.MedtronicPump ? //
|
||||
this.resourceId
|
||||
: this.resourceIdPod;
|
||||
} else {
|
||||
return this.resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
|
||||
/**
|
||||
* Created by andy on 14/05/2018.
|
||||
*/
|
||||
|
||||
public enum RileyLinkServiceState {
|
||||
|
||||
NotStarted(R.string.rileylink_state_not_started), //
|
||||
|
||||
// Bluetooth
|
||||
BluetoothInitializing(R.string.rileylink_state_bt_init), // (S) init BT (if error no BT interface -> Disabled, BT
|
||||
// not enabled -> BluetoothError)
|
||||
// BluetoothNotAvailable, // (E) BT not available, would happen only if device has no BT
|
||||
BluetoothError(R.string.rileylink_state_bt_error), // (E) if BT gets disabled ( -> EnableBluetooth)
|
||||
BluetoothReady(R.string.rileylink_state_bt_ready), // (OK)
|
||||
|
||||
// RileyLink
|
||||
RileyLinkInitializing(R.string.rileylink_state_rl_init), // (S) start Gatt discovery (OK -> RileyLinkReady, Error ->
|
||||
// BluetoothEnabled) ??
|
||||
RileyLinkError(R.string.rileylink_state_rl_error), // (E)
|
||||
RileyLinkReady(R.string.rileylink_state_connected), // (OK) if tunning was already done we go to PumpConnectorReady
|
||||
|
||||
// Tunning
|
||||
TuneUpDevice(R.string.rileylink_state_pc_tune_up), // (S)
|
||||
PumpConnectorError(R.string.rileylink_state_pc_error), // either TuneUp Error or pump couldn't not be contacted
|
||||
// error
|
||||
PumpConnectorReady(R.string.rileylink_state_connected), // (OK) RileyLink Ready for Pump Communication
|
||||
|
||||
// Initializing, // get all parameters required for connection (if not possible -> Disabled, if sucessful ->
|
||||
// EnableBluetooth)
|
||||
|
||||
// EnableBlueTooth, // enable BT (if error no BT interface -> Disabled, BT not enabled -> BluetoothError)
|
||||
// BlueToothEnabled, // -> InitializeRileyLink
|
||||
// RileyLinkInitialized, //
|
||||
|
||||
// RileyLinkConnected, // -> TuneUpPump (on 1st), else PumpConnectorReady
|
||||
|
||||
// PumpConnected, //
|
||||
|
||||
;
|
||||
|
||||
int resourceId;
|
||||
Integer resourceIdPod;
|
||||
|
||||
|
||||
RileyLinkServiceState(int resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
|
||||
RileyLinkServiceState(int resourceId, int resourceIdPod) {
|
||||
this.resourceId = resourceId;
|
||||
this.resourceIdPod = resourceIdPod;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isReady(RileyLinkServiceState serviceState) {
|
||||
return (/* serviceState == RileyLinkReady || */serviceState == PumpConnectorReady);
|
||||
}
|
||||
|
||||
|
||||
public int getResourceId(RileyLinkTargetDevice targetDevice) {
|
||||
if (this.resourceIdPod != null) {
|
||||
|
||||
return (targetDevice == null || targetDevice == RileyLinkTargetDevice.MedtronicPump) ? //
|
||||
this.resourceId
|
||||
: this.resourceIdPod;
|
||||
} else {
|
||||
return this.resourceId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isConnecting() {
|
||||
|
||||
return (this == RileyLinkServiceState.BluetoothInitializing || //
|
||||
// this == RileyLinkServiceState.BluetoothError || //
|
||||
this == RileyLinkServiceState.BluetoothReady || //
|
||||
this == RileyLinkServiceState.RileyLinkInitializing || //
|
||||
this == RileyLinkReady
|
||||
// this == RileyLinkServiceState.RileyLinkBLEError
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public boolean isError() {
|
||||
|
||||
return (this == RileyLinkServiceState.BluetoothError || //
|
||||
// this == RileyLinkServiceState.PumpConnectorError || //
|
||||
this == RileyLinkServiceState.RileyLinkError);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
public enum RileyLinkTargetDevice {
|
||||
MedtronicPump(R.string.rileylink_target_device_medtronic, true), //
|
||||
Omnipod(R.string.rileylink_target_device_omnipod, false), //
|
||||
;
|
||||
|
||||
private int resourceId;
|
||||
private boolean tuneUpEnabled;
|
||||
|
||||
|
||||
RileyLinkTargetDevice(int resourceId, boolean tuneUpEnabled) {
|
||||
this.resourceId = resourceId;
|
||||
this.tuneUpEnabled = tuneUpEnabled;
|
||||
}
|
||||
|
||||
|
||||
public boolean isTuneUpEnabled() {
|
||||
return tuneUpEnabled;
|
||||
}
|
||||
|
||||
|
||||
public int getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import butterknife.BindView;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||
|
||||
public class RileyLinkStatusActivity extends AppCompatActivity {
|
||||
|
||||
@BindView(R.id.rls_t1_connection_status)
|
||||
TextView connectionStatus;
|
||||
|
||||
@BindView(R.id.rls_t1_configured_address)
|
||||
TextView configuredAddress;
|
||||
|
||||
@BindView(R.id.rls_t1_connected_device)
|
||||
TextView connectedDevice;
|
||||
|
||||
@BindView(R.id.rls_t1_connection_error)
|
||||
TextView connectionError;
|
||||
RileyLinkServiceData rileyLinkServiceData;
|
||||
|
||||
private SectionsPagerAdapter mSectionsPagerAdapter;
|
||||
private FloatingActionButton floatingActionButton;
|
||||
private TabLayout tabLayout;
|
||||
/**
|
||||
* The {@link ViewPager} that will host the section contents.
|
||||
*/
|
||||
private ViewPager mViewPager;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.rileylink_status);
|
||||
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
// primary sections of the activity.
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
mViewPager = (ViewPager)findViewById(R.id.rileylink_settings_container);
|
||||
// mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
setupViewPager(mViewPager);
|
||||
|
||||
tabLayout = (TabLayout)findViewById(R.id.rileylink_settings_tabs);
|
||||
tabLayout.setupWithViewPager(mViewPager);
|
||||
|
||||
floatingActionButton = (FloatingActionButton)findViewById(R.id.rileylink_settings_fab);
|
||||
floatingActionButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
RefreshableInterface selectableInterface = (RefreshableInterface)mSectionsPagerAdapter
|
||||
.getItem(tabLayout.getSelectedTabPosition());
|
||||
selectableInterface.refreshData();
|
||||
|
||||
// refreshData(tabLayout.getSelectedTabPosition());
|
||||
|
||||
// Toast.makeText(getApplicationContext(), "Test pos: " + tabLayout.getSelectedTabPosition(),
|
||||
// Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
|
||||
this.connectionStatus = (TextView)findViewById(R.id.rls_t1_connection_status);
|
||||
this.configuredAddress = (TextView)findViewById(R.id.rls_t1_configured_address);
|
||||
this.connectedDevice = (TextView)findViewById(R.id.rls_t1_connected_device);
|
||||
this.connectionError = (TextView)findViewById(R.id.rls_t1_connection_error);
|
||||
|
||||
rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||
|
||||
// // 7-12
|
||||
// int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, //
|
||||
// R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12};
|
||||
//
|
||||
// for (int id : ids) {
|
||||
//
|
||||
// TextView tv = (TextView) findViewById(id);
|
||||
// tv.setText(tv.getText() + ":");
|
||||
// }
|
||||
|
||||
// refreshData(0);
|
||||
// refreshData(1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void refreshData(int position) {
|
||||
if (position == 0) {
|
||||
// FIXME i18n
|
||||
this.connectionStatus.setText(rileyLinkServiceData.serviceState.name());
|
||||
this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress);
|
||||
// FIXME
|
||||
this.connectedDevice.setText("???");
|
||||
// FIXME i18n
|
||||
this.connectionError.setText(rileyLinkServiceData.errorCode.name());
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setupViewPager(ViewPager pager) {
|
||||
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||
|
||||
mSectionsPagerAdapter.addFragment(new RileyLinkStatusGeneral(), MainApp.gs(R.string.rileylink_settings_tab1));
|
||||
mSectionsPagerAdapter.addFragment(new RileyLinkStatusHistory(), MainApp.gs(R.string.rileylink_settings_tab2));
|
||||
//mSectionsPagerAdapter.addFragment(new RileyLinkStatusDevice(), "Medtronic");
|
||||
|
||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
||||
* one of the sections/tabs/pages.
|
||||
*/
|
||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> fragmentTitle = new ArrayList<>();
|
||||
int lastSelectedPosition = 0;
|
||||
|
||||
|
||||
public SectionsPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
this.lastSelectedPosition = position;
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// Show 3 total pages.
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
|
||||
public void addFragment(Fragment fragment, String title) {
|
||||
this.fragmentList.add(fragment);
|
||||
this.fragmentTitle.add(title);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return fragmentTitle.get(position);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition;
|
||||
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||
//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
||||
//import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
// FIXME needs to be implemented
|
||||
|
||||
public class RileyLinkStatusDevice extends Fragment implements RefreshableInterface {
|
||||
|
||||
ListView listView;
|
||||
|
||||
RileyLinkCommandListAdapter adapter;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.rileylink_status_device, container, false);
|
||||
|
||||
adapter = new RileyLinkCommandListAdapter();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
this.listView = (ListView)getActivity().findViewById(R.id.rileyLinkDeviceList);
|
||||
|
||||
listView.setAdapter(adapter);
|
||||
|
||||
setElements();
|
||||
}
|
||||
|
||||
|
||||
private void setElements() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void refreshData() {
|
||||
// adapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory());
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
|
||||
TextView itemDescription;
|
||||
Button itemValue;
|
||||
}
|
||||
|
||||
private class RileyLinkCommandListAdapter extends BaseAdapter {
|
||||
|
||||
private List<CommandValueDefinition> commandValueList;
|
||||
private Map<CommandValueDefinitionType, CommandValueDefinition> commandValueMap;
|
||||
private LayoutInflater mInflator;
|
||||
|
||||
|
||||
public RileyLinkCommandListAdapter() {
|
||||
super();
|
||||
commandValueList = new ArrayList<>();
|
||||
mInflator = RileyLinkStatusDevice.this.getLayoutInflater();
|
||||
}
|
||||
|
||||
|
||||
public void addItems(List<CommandValueDefinition> list) {
|
||||
commandValueList.addAll(list);
|
||||
|
||||
for (CommandValueDefinition commandValueDefinition : list) {
|
||||
commandValueMap.put(commandValueDefinition.definitionType, commandValueDefinition);
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
public CommandValueDefinition getCommandValueItem(int position) {
|
||||
return commandValueList.get(position);
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
commandValueList.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return commandValueList.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getItem(int i) {
|
||||
return commandValueList.get(i);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getItemId(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||
RileyLinkStatusDevice.ViewHolder viewHolder;
|
||||
// General ListView optimization code.
|
||||
if (view == null) {
|
||||
view = mInflator.inflate(R.layout.rileylink_status_device_item, null);
|
||||
viewHolder = new RileyLinkStatusDevice.ViewHolder();
|
||||
viewHolder.itemDescription = (TextView)view.findViewById(R.id.rileylink_device_label);
|
||||
viewHolder.itemValue = (Button)view.findViewById(R.id.rileylink_device_action);
|
||||
view.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (RileyLinkStatusDevice.ViewHolder)view.getTag();
|
||||
}
|
||||
// Z
|
||||
// RLHistoryItem item = historyItemList.get(i);
|
||||
// viewHolder.itemTime.setText(StringUtil.toDateTimeString(item.getDateTime()));
|
||||
// viewHolder.itemSource.setText("Riley Link"); // for now
|
||||
// viewHolder.itemDescription.setText(item.getDescription());
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
public class RileyLinkStatusGeneral extends Fragment implements RefreshableInterface {
|
||||
|
||||
TextView connectionStatus;
|
||||
TextView configuredAddress;
|
||||
TextView connectedDevice;
|
||||
TextView connectionError;
|
||||
TextView deviceType;
|
||||
TextView deviceModel;
|
||||
TextView serialNumber;
|
||||
TextView pumpFrequency;
|
||||
TextView lastUsedFrequency;
|
||||
TextView lastDeviceContact;
|
||||
TextView firmwareVersion;
|
||||
|
||||
RileyLinkServiceData rileyLinkServiceData;
|
||||
|
||||
MedtronicPumpStatus medtronicPumpStatus;
|
||||
boolean first = false;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.rileylink_status_general, container, false);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
|
||||
|
||||
this.connectionStatus = getActivity().findViewById(R.id.rls_t1_connection_status);
|
||||
this.configuredAddress = getActivity().findViewById(R.id.rls_t1_configured_address);
|
||||
this.connectedDevice = getActivity().findViewById(R.id.rls_t1_connected_device);
|
||||
this.connectionError = getActivity().findViewById(R.id.rls_t1_connection_error);
|
||||
this.deviceType = getActivity().findViewById(R.id.rls_t1_device_type);
|
||||
this.deviceModel = getActivity().findViewById(R.id.rls_t1_device_model);
|
||||
this.serialNumber = getActivity().findViewById(R.id.rls_t1_serial_number);
|
||||
this.pumpFrequency = getActivity().findViewById(R.id.rls_t1_pump_frequency);
|
||||
this.lastUsedFrequency = getActivity().findViewById(R.id.rls_t1_last_used_frequency);
|
||||
this.lastDeviceContact = getActivity().findViewById(R.id.rls_t1_last_device_contact);
|
||||
this.firmwareVersion = getActivity().findViewById(R.id.rls_t1_firmware_version);
|
||||
|
||||
if (!first) {
|
||||
|
||||
// 7-12
|
||||
int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, //
|
||||
R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12, R.id.rls_t1_tv13};
|
||||
|
||||
for (int id : ids) {
|
||||
|
||||
TextView tv = (TextView) getActivity().findViewById(id);
|
||||
tv.setText(tv.getText() + ":");
|
||||
}
|
||||
|
||||
first = true;
|
||||
}
|
||||
|
||||
refreshData();
|
||||
}
|
||||
|
||||
|
||||
public void refreshData() {
|
||||
|
||||
RileyLinkTargetDevice targetDevice = RileyLinkUtil.getTargetDevice();
|
||||
|
||||
this.connectionStatus.setText(MainApp.gs(RileyLinkUtil.getServiceState().getResourceId(targetDevice)));
|
||||
|
||||
if (rileyLinkServiceData != null) {
|
||||
this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress);
|
||||
this.connectionError.setText(rileyLinkServiceData.errorCode == null ? //
|
||||
"-"
|
||||
: MainApp.gs(rileyLinkServiceData.errorCode.getResourceId(targetDevice)));
|
||||
|
||||
|
||||
RileyLinkFirmwareVersion firmwareVersion = rileyLinkServiceData.versionCC110;
|
||||
|
||||
if (firmwareVersion==null) {
|
||||
this.firmwareVersion.setText("BLE113: -\nCC110: -");
|
||||
} else {
|
||||
this.firmwareVersion.setText("BLE113: " + rileyLinkServiceData.versionBLE113 + //
|
||||
"\nCC110: " + firmwareVersion.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO add handling for Omnipod pump status
|
||||
this.medtronicPumpStatus = MedtronicUtil.getPumpStatus();
|
||||
|
||||
if (medtronicPumpStatus != null) {
|
||||
this.deviceType.setText(MainApp.gs(RileyLinkUtil.getTargetDevice().getResourceId()));
|
||||
this.deviceModel.setText(medtronicPumpStatus.pumpType.getDescription());
|
||||
this.serialNumber.setText(medtronicPumpStatus.serialNumber);
|
||||
this.pumpFrequency.setText(medtronicPumpStatus.pumpFrequency);
|
||||
|
||||
// TODO extend when Omnipod used
|
||||
|
||||
if (MedtronicUtil.getMedtronicPumpModel() != null)
|
||||
this.connectedDevice.setText("Medtronic " + MedtronicUtil.getMedtronicPumpModel().getPumpModel());
|
||||
else
|
||||
this.connectedDevice.setText("???");
|
||||
|
||||
if (rileyLinkServiceData.lastGoodFrequency != null)
|
||||
this.lastUsedFrequency.setText(String.format(Locale.ENGLISH, "%.2f MHz",
|
||||
rileyLinkServiceData.lastGoodFrequency));
|
||||
|
||||
if (medtronicPumpStatus.lastConnection != 0)
|
||||
this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime(
|
||||
medtronicPumpStatus.lastDataTime)));
|
||||
else
|
||||
this.lastDeviceContact.setText("Never");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/19/18.
|
||||
*/
|
||||
|
||||
public class RileyLinkStatusHistory extends Fragment implements RefreshableInterface {
|
||||
|
||||
RecyclerView recyclerView;
|
||||
RecyclerViewAdapter recyclerViewAdapter;
|
||||
|
||||
LinearLayoutManager llm;
|
||||
List<RLHistoryItem> filteredHistoryList = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.rileylink_status_history, container, false);
|
||||
|
||||
recyclerView = (RecyclerView)rootView.findViewById(R.id.rileylink_history_list);
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
llm = new LinearLayoutManager(getActivity().getApplicationContext());
|
||||
recyclerView.setLayoutManager(llm);
|
||||
|
||||
recyclerViewAdapter = new RecyclerViewAdapter(filteredHistoryList);
|
||||
recyclerView.setAdapter(recyclerViewAdapter);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
refreshData();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void refreshData() {
|
||||
if (RileyLinkUtil.getRileyLinkHistory()!=null) {
|
||||
recyclerViewAdapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.HistoryViewHolder> {
|
||||
|
||||
List<RLHistoryItem> historyList;
|
||||
|
||||
|
||||
RecyclerViewAdapter(List<RLHistoryItem> historyList) {
|
||||
this.historyList = historyList;
|
||||
}
|
||||
|
||||
|
||||
public void setHistoryList(List<RLHistoryItem> historyList) {
|
||||
this.historyList = historyList;
|
||||
}
|
||||
|
||||
|
||||
public void addItemsAndClean(List<RLHistoryItem> items) {
|
||||
this.historyList.clear();
|
||||
|
||||
for (RLHistoryItem item : items) {
|
||||
|
||||
if (!historyList.contains(item) && isValidItem(item)) {
|
||||
historyList.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
private boolean isValidItem(RLHistoryItem item) {
|
||||
|
||||
PumpDeviceState pumpState = item.getPumpDeviceState();
|
||||
|
||||
if ((pumpState != null) && //
|
||||
(pumpState == PumpDeviceState.Sleeping || //
|
||||
pumpState == PumpDeviceState.Active || //
|
||||
pumpState == PumpDeviceState.WakingUp //
|
||||
))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RecyclerViewAdapter.HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rileylink_status_history_item, //
|
||||
viewGroup, false);
|
||||
return new RecyclerViewAdapter.HistoryViewHolder(v);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerViewAdapter.HistoryViewHolder holder, int position) {
|
||||
RLHistoryItem item = historyList.get(position);
|
||||
|
||||
if (item != null) {
|
||||
holder.timeView.setText(StringUtil.toDateTimeString(item.getDateTime()));
|
||||
holder.typeView.setText(item.getSource().getDesc());
|
||||
holder.valueView.setText(item.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return historyList.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
static class HistoryViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView timeView;
|
||||
TextView typeView;
|
||||
TextView valueView;
|
||||
|
||||
|
||||
HistoryViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
timeView = (TextView)itemView.findViewById(R.id.rileylink_history_time);
|
||||
typeView = (TextView)itemView.findViewById(R.id.rileylink_history_source);
|
||||
valueView = (TextView)itemView.findViewById(R.id.rileylink_history_description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.interfaces.PumpInterface;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
|
||||
public class RileyLinkBluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(L.PUMP);
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
|
||||
PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump();
|
||||
|
||||
if (action != null && activePump != null) {
|
||||
|
||||
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
|
||||
switch (state) {
|
||||
case BluetoothAdapter.STATE_OFF:
|
||||
case BluetoothAdapter.STATE_TURNING_OFF:
|
||||
case BluetoothAdapter.STATE_TURNING_ON:
|
||||
break;
|
||||
|
||||
case BluetoothAdapter.STATE_ON: {
|
||||
LOG.debug("RileyLinkBluetoothStateReceiver: Bluetooth back on. Sending broadcast to RileyLink Framework");
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothReconnected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void unregisterBroadcasts() {
|
||||
MainApp.instance().unregisterReceiver(this);
|
||||
}
|
||||
|
||||
|
||||
public void registerBroadcasts() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
|
||||
MainApp.instance().registerReceiver(this, filter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||
|
||||
/**
|
||||
* Created by andy on 10/23/18.
|
||||
*/
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.DiscoverGattServicesTask;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.InitializePumpManagerTask;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
/**
|
||||
* I added this class outside of RileyLinkService, because for now it's very important part of RL framework and
|
||||
* where we get a lot of problems. Especially merging between AAPS and RileyLinkAAPS. I might put it back at
|
||||
* later time
|
||||
*/
|
||||
public class RileyLinkBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||
|
||||
RileyLinkService serviceInstance;
|
||||
protected Map<String, List<String>> broadcastIdentifiers = null;
|
||||
String deviceSpecificPrefix;
|
||||
Context context;
|
||||
|
||||
|
||||
public RileyLinkBroadcastReceiver(RileyLinkService serviceInstance, Context context) {
|
||||
this.serviceInstance = serviceInstance;
|
||||
this.context = context;
|
||||
|
||||
createBroadcastIdentifiers();
|
||||
}
|
||||
|
||||
|
||||
private void createBroadcastIdentifiers() {
|
||||
|
||||
this.broadcastIdentifiers = new HashMap<>();
|
||||
|
||||
// Bluetooth
|
||||
this.broadcastIdentifiers.put("Bluetooth", Arrays.asList( //
|
||||
RileyLinkConst.Intents.BluetoothConnected, //
|
||||
RileyLinkConst.Intents.BluetoothReconnected));
|
||||
|
||||
// TuneUp
|
||||
this.broadcastIdentifiers.put("TuneUp", Arrays.asList( //
|
||||
RileyLinkConst.IPC.MSG_PUMP_tunePump, //
|
||||
RileyLinkConst.IPC.MSG_PUMP_quickTune));
|
||||
|
||||
// RileyLink
|
||||
this.broadcastIdentifiers.put("RileyLink", Arrays.asList( //
|
||||
RileyLinkConst.Intents.RileyLinkDisconnected, //
|
||||
RileyLinkConst.Intents.RileyLinkReady, //
|
||||
RileyLinkConst.Intents.RileyLinkDisconnected, //
|
||||
RileyLinkConst.Intents.RileyLinkNewAddressSet, //
|
||||
RileyLinkConst.Intents.RileyLinkDisconnect));
|
||||
|
||||
// Device Specific
|
||||
deviceSpecificPrefix = serviceInstance.getDeviceSpecificBroadcastsIdentifierPrefix();
|
||||
|
||||
// Application specific
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
if (intent == null) {
|
||||
LOG.error("onReceive: received null intent");
|
||||
} else {
|
||||
String action = intent.getAction();
|
||||
if (action == null) {
|
||||
LOG.error("onReceive: null action");
|
||||
} else {
|
||||
if (isLoggingEnabled())
|
||||
LOG.debug("Received Broadcast: " + action);
|
||||
|
||||
if (!processBluetoothBroadcasts(action) && //
|
||||
!processRileyLinkBroadcasts(action) && //
|
||||
!processTuneUpBroadcasts(action) && //
|
||||
!processDeviceSpecificBroadcasts(action, intent) && //
|
||||
!processApplicationSpecificBroadcasts(action, intent) //
|
||||
) {
|
||||
LOG.error("Unhandled broadcast: action=" + action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void registerBroadcasts() {
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
|
||||
for (Map.Entry<String, List<String>> stringListEntry : broadcastIdentifiers.entrySet()) {
|
||||
|
||||
for (String intentKey : stringListEntry.getValue()) {
|
||||
intentFilter.addAction(intentKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceSpecificPrefix != null) {
|
||||
serviceInstance.registerDeviceSpecificBroadcasts(intentFilter);
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter);
|
||||
}
|
||||
|
||||
|
||||
private boolean processRileyLinkBroadcasts(String action) {
|
||||
|
||||
if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnected)) {
|
||||
if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
|
||||
RileyLinkUtil
|
||||
.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.RileyLinkUnreachable);
|
||||
} else {
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (action.equals(RileyLinkConst.Intents.RileyLinkReady)) {
|
||||
|
||||
if (isLoggingEnabled())
|
||||
LOG.warn("MedtronicConst.Intents.RileyLinkReady");
|
||||
// sendIPCNotification(RT2Const.IPC.MSG_note_WakingPump);
|
||||
|
||||
if (this.serviceInstance.rileyLinkBLE == null)
|
||||
return false;
|
||||
|
||||
this.serviceInstance.rileyLinkBLE.enableNotifications();
|
||||
this.serviceInstance.rfspy.startReader(); // call startReader from outside?
|
||||
|
||||
this.serviceInstance.rfspy.initializeRileyLink();
|
||||
String bleVersion = this.serviceInstance.rfspy.getBLEVersionCached();
|
||||
RileyLinkFirmwareVersion rlVersion = this.serviceInstance.rfspy.getRLVersionCached();
|
||||
|
||||
// if (isLoggingEnabled())
|
||||
LOG.debug("RfSpy version (BLE113): " + bleVersion);
|
||||
this.serviceInstance.rileyLinkServiceData.versionBLE113 = bleVersion;
|
||||
|
||||
// if (isLoggingEnabled())
|
||||
LOG.debug("RfSpy Radio version (CC110): " + rlVersion.name());
|
||||
this.serviceInstance.rileyLinkServiceData.versionCC110 = rlVersion;
|
||||
|
||||
ServiceTask task = new InitializePumpManagerTask(RileyLinkUtil.getTargetDevice());
|
||||
ServiceTaskExecutor.startTask(task);
|
||||
if (isLoggingEnabled())
|
||||
LOG.info("Announcing RileyLink open For business");
|
||||
|
||||
return true;
|
||||
} else if (action.equals(RileyLinkConst.Intents.RileyLinkNewAddressSet)) {
|
||||
String RileylinkBLEAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, "");
|
||||
if (RileylinkBLEAddress.equals("")) {
|
||||
LOG.error("No Rileylink BLE Address saved in app");
|
||||
} else {
|
||||
// showBusy("Configuring Service", 50);
|
||||
// rileyLinkBLE.findRileyLink(RileylinkBLEAddress);
|
||||
this.serviceInstance.reconfigureRileyLink(RileylinkBLEAddress);
|
||||
// MainApp.getServiceClientConnection().setThisRileylink(RileylinkBLEAddress);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnect)) {
|
||||
this.serviceInstance.disconnectRileyLink();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean processBluetoothBroadcasts(String action) {
|
||||
|
||||
if (action.equals(RileyLinkConst.Intents.BluetoothConnected)) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.debug("Bluetooth - Connected");
|
||||
ServiceTaskExecutor.startTask(new DiscoverGattServicesTask());
|
||||
|
||||
return true;
|
||||
|
||||
} else if (action.equals(RileyLinkConst.Intents.BluetoothReconnected)) {
|
||||
if (isLoggingEnabled())
|
||||
LOG.debug("Bluetooth - Reconnecting");
|
||||
|
||||
serviceInstance.bluetoothInit();
|
||||
ServiceTaskExecutor.startTask(new DiscoverGattServicesTask(true));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private boolean processTuneUpBroadcasts(String action) {
|
||||
|
||||
if (this.broadcastIdentifiers.get("TuneUp").contains(action)) {
|
||||
if (serviceInstance.getRileyLinkTargetDevice().isTuneUpEnabled()) {
|
||||
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean processDeviceSpecificBroadcasts(String action, Intent intent) {
|
||||
|
||||
if (this.deviceSpecificPrefix == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action.startsWith(this.deviceSpecificPrefix)) {
|
||||
return this.serviceInstance.handleDeviceSpecificBroadcasts(intent);
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean processApplicationSpecificBroadcasts(String action, Intent intent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isLoggingEnabled() {
|
||||
return (L.isEnabled(L.PUMPCOMM));
|
||||
}
|
||||
|
||||
public void unregisterBroadcasts() {
|
||||
LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||
|
||||
import static info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil.getRileyLinkCommunicationManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import android.app.Service;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
/**
|
||||
* Created by andy on 5/6/18.
|
||||
* Split from original file and renamed.
|
||||
*/
|
||||
public abstract class RileyLinkService extends Service {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||
|
||||
public RileyLinkBLE rileyLinkBLE; // android-bluetooth management
|
||||
protected BluetoothAdapter bluetoothAdapter;
|
||||
protected RFSpy rfspy; // interface for RL xxx Mhz radio.
|
||||
protected Context context;
|
||||
protected RileyLinkBroadcastReceiver mBroadcastReceiver;
|
||||
protected RileyLinkServiceData rileyLinkServiceData;
|
||||
protected RileyLinkBluetoothStateReceiver bluetoothStateReceiver;
|
||||
|
||||
public RileyLinkService(Context context) {
|
||||
super();
|
||||
this.context = context;
|
||||
RileyLinkUtil.setContext(this.context);
|
||||
RileyLinkUtil.setRileyLinkService(this);
|
||||
RileyLinkUtil.setEncoding(getEncoding());
|
||||
initRileyLinkServiceData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Encoding for RileyLink communication
|
||||
*/
|
||||
public abstract RileyLinkEncodingType getEncoding();
|
||||
|
||||
|
||||
/**
|
||||
* If you have customized RileyLinkServiceData you need to override this
|
||||
*/
|
||||
public abstract void initRileyLinkServiceData();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
//LOG.warn("onUnbind");
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRebind(Intent intent) {
|
||||
//LOG.warn("onRebind");
|
||||
super.onRebind(intent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
//LOG.error("I die! I die!");
|
||||
|
||||
if (rileyLinkBLE != null) {
|
||||
rileyLinkBLE.disconnect(); // dispose of Gatt (disconnect and close)
|
||||
rileyLinkBLE = null;
|
||||
}
|
||||
|
||||
if (mBroadcastReceiver!=null) {
|
||||
mBroadcastReceiver.unregisterBroadcasts();
|
||||
}
|
||||
|
||||
if (bluetoothStateReceiver!=null) {
|
||||
bluetoothStateReceiver.unregisterBroadcasts();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
//LOG.debug("onCreate");
|
||||
|
||||
mBroadcastReceiver = new RileyLinkBroadcastReceiver(this, this.context);
|
||||
mBroadcastReceiver.registerBroadcasts();
|
||||
|
||||
|
||||
bluetoothStateReceiver = new RileyLinkBluetoothStateReceiver();
|
||||
bluetoothStateReceiver.registerBroadcasts();
|
||||
|
||||
//LOG.debug("onCreate(): It's ALIVE!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prefix for Device specific broadcast identifier prefix (for example MSG_PUMP_ for pump or
|
||||
* MSG_POD_ for Omnipod)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract String getDeviceSpecificBroadcastsIdentifierPrefix();
|
||||
|
||||
|
||||
public abstract boolean handleDeviceSpecificBroadcasts(Intent intent);
|
||||
|
||||
|
||||
public abstract void registerDeviceSpecificBroadcasts(IntentFilter intentFilter);
|
||||
|
||||
|
||||
public abstract RileyLinkCommunicationManager getDeviceCommunicationManager();
|
||||
|
||||
|
||||
// Here is where the wake-lock begins:
|
||||
// We've received a service startCommand, we grab the lock.
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
RileyLinkUtil.setContext(getApplicationContext());
|
||||
return (START_STICKY);
|
||||
}
|
||||
|
||||
|
||||
public boolean bluetoothInit() {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("bluetoothInit: attempting to get an adapter");
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing);
|
||||
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (bluetoothAdapter == null) {
|
||||
LOG.error("Unable to obtain a BluetoothAdapter.");
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.NoBluetoothAdapter);
|
||||
} else {
|
||||
|
||||
if (!bluetoothAdapter.isEnabled()) {
|
||||
LOG.error("Bluetooth is not enabled.");
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled);
|
||||
} else {
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// returns true if our Rileylink configuration changed
|
||||
public boolean reconfigureRileyLink(String deviceAddress) {
|
||||
|
||||
if (rileyLinkBLE == null) {
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing);
|
||||
return false;
|
||||
}
|
||||
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkInitializing);
|
||||
|
||||
if (rileyLinkBLE.isConnected()) {
|
||||
if (deviceAddress.equals(rileyLinkServiceData.rileylinkAddress)) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("No change to RL address. Not reconnecting.");
|
||||
return false;
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.warn("Disconnecting from old RL (" + rileyLinkServiceData.rileylinkAddress
|
||||
+ "), reconnecting to new: " + deviceAddress);
|
||||
|
||||
rileyLinkBLE.disconnect();
|
||||
// prolly need to shut down listening thread too?
|
||||
// SP.putString(MedtronicConst.Prefs.RileyLinkAddress, deviceAddress);
|
||||
|
||||
rileyLinkServiceData.rileylinkAddress = deviceAddress;
|
||||
rileyLinkBLE.findRileyLink(rileyLinkServiceData.rileylinkAddress);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (isLogEnabled())
|
||||
LOG.debug("Using RL " + deviceAddress);
|
||||
|
||||
if (RileyLinkUtil.getServiceState() == RileyLinkServiceState.NotStarted) {
|
||||
if (!bluetoothInit()) {
|
||||
LOG.error("RileyLink can't get activated, Bluetooth is not functioning correctly. {}",
|
||||
RileyLinkUtil.getError() != null ? RileyLinkUtil.getError().name() : "Unknown error (null)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
rileyLinkBLE.findRileyLink(deviceAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) {
|
||||
}
|
||||
|
||||
|
||||
// FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc.
|
||||
public void doTuneUpDevice() {
|
||||
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.TuneUpDevice);
|
||||
MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping);
|
||||
|
||||
double lastGoodFrequency = 0.0d;
|
||||
|
||||
if (rileyLinkServiceData.lastGoodFrequency == null) {
|
||||
lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d);
|
||||
} else {
|
||||
lastGoodFrequency = rileyLinkServiceData.lastGoodFrequency;
|
||||
}
|
||||
|
||||
double newFrequency;
|
||||
|
||||
newFrequency = getDeviceCommunicationManager().tuneForDevice();
|
||||
|
||||
if ((newFrequency != 0.0) && (newFrequency != lastGoodFrequency)) {
|
||||
if (isLogEnabled())
|
||||
LOG.info("Saving new pump frequency of {} MHz", newFrequency);
|
||||
SP.putDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, newFrequency);
|
||||
rileyLinkServiceData.lastGoodFrequency = newFrequency;
|
||||
rileyLinkServiceData.tuneUpDone = true;
|
||||
rileyLinkServiceData.lastTuneUpTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (newFrequency == 0.0d) {
|
||||
// error tuning pump, pump not present ??
|
||||
RileyLinkUtil
|
||||
.setServiceState(RileyLinkServiceState.PumpConnectorError, RileyLinkError.TuneUpOfDeviceFailed);
|
||||
} else {
|
||||
getRileyLinkCommunicationManager().clearNotConnectedCount();
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void disconnectRileyLink() {
|
||||
|
||||
if (this.rileyLinkBLE != null && this.rileyLinkBLE.isConnected()) {
|
||||
this.rileyLinkBLE.disconnect();
|
||||
rileyLinkServiceData.rileylinkAddress = null;
|
||||
}
|
||||
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Target Device for Service
|
||||
*/
|
||||
public RileyLinkTargetDevice getRileyLinkTargetDevice() {
|
||||
return this.rileyLinkServiceData.targetDevice;
|
||||
}
|
||||
|
||||
|
||||
private boolean isLogEnabled() {
|
||||
return L.isEnabled(L.PUMPCOMM);
|
||||
}
|
||||
|
||||
|
||||
public void changeRileyLinkEncoding(RileyLinkEncodingType encodingType) {
|
||||
if (rfspy != null) {
|
||||
rfspy.setRileyLinkEncoding(encodingType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
|
||||
/**
|
||||
* Created by andy on 16/05/2018.
|
||||
*/
|
||||
|
||||
public class RileyLinkServiceData {
|
||||
|
||||
public boolean tuneUpDone = false;
|
||||
public RileyLinkError errorCode;
|
||||
public RileyLinkServiceState serviceState = RileyLinkServiceState.NotStarted;
|
||||
public String rileylinkAddress;
|
||||
public long lastTuneUpTime = 0L;
|
||||
public Double lastGoodFrequency;
|
||||
|
||||
// bt version
|
||||
public String versionBLE113;
|
||||
// radio version
|
||||
public RileyLinkFirmwareVersion versionCC110;
|
||||
|
||||
public RileyLinkTargetDevice targetDevice;
|
||||
|
||||
// Medtronic Pump
|
||||
public String pumpID;
|
||||
public byte[] pumpIDBytes;
|
||||
|
||||
|
||||
public RileyLinkServiceData(RileyLinkTargetDevice targetDevice) {
|
||||
this.targetDevice = targetDevice;
|
||||
}
|
||||
|
||||
|
||||
public void setPumpID(String pumpId, byte[] pumpIdBytes) {
|
||||
this.pumpID = pumpId;
|
||||
this.pumpIDBytes = pumpIdBytes;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Created by geoff on 6/25/16.
|
||||
*/
|
||||
public class ServiceCommand extends ServiceMessage {
|
||||
|
||||
public ServiceCommand() {
|
||||
map = new Bundle();
|
||||
}
|
||||
|
||||
|
||||
// commandID is a string that the client can set on the message.
|
||||
// The service does not use this value, but passes it back with the result
|
||||
// so that the client can identify it.
|
||||
public ServiceCommand(String commandName, String commandID) {
|
||||
init();
|
||||
map.putString("command", commandName);
|
||||
map.putString("commandID", commandID);
|
||||
}
|
||||
|
||||
|
||||
public ServiceCommand(Bundle commandBundle) {
|
||||
if (commandBundle != null) {
|
||||
map = commandBundle;
|
||||
} else {
|
||||
map = new Bundle();
|
||||
init();
|
||||
map.putString("command", "(null)");
|
||||
map.putString("commandID", "(null");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
map.putString("ServiceMessageType", "ServiceCommand");
|
||||
}
|
||||
|
||||
|
||||
public String getCommandID() {
|
||||
return map.getString("commandID");
|
||||
}
|
||||
|
||||
|
||||
public String getCommandName() {
|
||||
return map.getString("command");
|
||||
}
|
||||
|
||||
|
||||
public boolean isPumpCommand() {
|
||||
switch (getCommandName()) {
|
||||
case "FetchPumpHistory":
|
||||
case "ReadPumpClock":
|
||||
case "RetrieveHistoryPage":
|
||||
case "ReadISFProfile":
|
||||
case "ReadBolusWizardCarbProfile":
|
||||
case "UpdatePumpStatus":
|
||||
case "WakeAndTune":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/4/16.
|
||||
* <p>
|
||||
* Base class for all messages passed between service and client
|
||||
*/
|
||||
public class ServiceMessage {
|
||||
|
||||
protected Bundle map = new Bundle();
|
||||
|
||||
|
||||
public ServiceMessage() {
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
public void init() {
|
||||
map.putString("ServiceMessageClass", this.getClass().getCanonicalName());
|
||||
map.putString("ServiceMessageType", this.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
public Bundle getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
public void setMap(Bundle map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
|
||||
public String getServiceMessageType() {
|
||||
return map.getString("ServiceMessageType");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/6/16.
|
||||
* <p>
|
||||
* These are "one liner" messages between client and service. Must still be contained within ServiceTransports
|
||||
*/
|
||||
public class ServiceNotification extends ServiceMessage {
|
||||
|
||||
public ServiceNotification() {
|
||||
}
|
||||
|
||||
|
||||
public ServiceNotification(Bundle b) {
|
||||
if (b != null) {
|
||||
if ("ServiceNotification".equals(b.getString("ServiceMessageType"))) {
|
||||
setMap(b);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ServiceNotification(String notificationType) {
|
||||
setNotificationType(notificationType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
map.putString("ServiceMessageType", "ServiceNotification");
|
||||
}
|
||||
|
||||
|
||||
public String getNotificationType() {
|
||||
return map.getString("NotificationType", "");
|
||||
}
|
||||
|
||||
|
||||
public void setNotificationType(String notificationType) {
|
||||
map.putString("NotificationType", notificationType);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Created by geoff on 6/25/16.
|
||||
*/
|
||||
public class ServiceResult extends ServiceMessage {
|
||||
|
||||
public static final int ERROR_MALFORMED_PUMP_RESPONSE = 1;
|
||||
public static final int ERROR_NULL_PUMP_RESPONSE = 2;
|
||||
public static final int ERROR_INVALID_PUMP_RESPONSE = 3;
|
||||
public static final int ERROR_PUMP_BUSY = 4;
|
||||
|
||||
|
||||
public ServiceResult() {
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
public ServiceResult(Bundle resultBundle) {
|
||||
if (resultBundle != null) {
|
||||
setMap(resultBundle);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final String getErrorDescription(int errorCode) {
|
||||
switch (errorCode) {
|
||||
case ERROR_MALFORMED_PUMP_RESPONSE:
|
||||
return "Malformed Pump Response";
|
||||
case ERROR_NULL_PUMP_RESPONSE:
|
||||
return "Null pump response";
|
||||
case ERROR_INVALID_PUMP_RESPONSE:
|
||||
return "Invalid pump response";
|
||||
case ERROR_PUMP_BUSY:
|
||||
return "A pump command session is already in progress";
|
||||
default:
|
||||
return "Unknown error code (" + errorCode + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
map.putString("ServiceMessageType", "ServiceResult");
|
||||
setServiceResultType(this.getClass().getSimpleName());
|
||||
setResultError(0, "Uninitialized ServiceResult");
|
||||
}
|
||||
|
||||
|
||||
public String getServiceResultType() {
|
||||
return map.getString("ServiceResultType", "ServiceResult");
|
||||
}
|
||||
|
||||
|
||||
public void setServiceResultType(String serviceResultType) {
|
||||
map.putString("ServiceResultType", serviceResultType);
|
||||
}
|
||||
|
||||
|
||||
public void setResultOK() {
|
||||
map.putString("result", "OK");
|
||||
}
|
||||
|
||||
|
||||
public void setResultError(int errorCode) {
|
||||
setResultError(errorCode, getErrorDescription(errorCode));
|
||||
}
|
||||
|
||||
|
||||
public void setResultError(int errorCode, String errorDescription) {
|
||||
map.putString("result", "error");
|
||||
map.putInt("errorCode", errorCode);
|
||||
map.putString("errorDescription", errorDescription);
|
||||
}
|
||||
|
||||
|
||||
public boolean resultIsOK() {
|
||||
return ("OK".equals(map.getString("result", "")));
|
||||
}
|
||||
|
||||
|
||||
public String getErrorDescription() {
|
||||
return map.getString("errorDescription", "");
|
||||
}
|
||||
|
||||
|
||||
public String getResult() {
|
||||
return map.getString("result", "");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/6/16.
|
||||
* <p>
|
||||
* This class exists to hold a ServiceCommand along with transport variables such as time sent, time received, sender.
|
||||
* May also contain result, if the command is completed.
|
||||
*/
|
||||
public class ServiceTransport extends ServiceMessage {
|
||||
|
||||
private ServiceTransportType serviceTransportType = ServiceTransportType.Undefined;
|
||||
|
||||
|
||||
public ServiceTransport() {
|
||||
}
|
||||
|
||||
|
||||
public ServiceTransport(Bundle b) {
|
||||
if (b != null) {
|
||||
if ("ServiceTransport".equals(b.getString("ServiceMessageType"))) {
|
||||
setMap(b);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
map.putString("ServiceMessageType", "ServiceTransport");
|
||||
setTransportType("unknown");
|
||||
setSenderHashcode(0);
|
||||
}
|
||||
|
||||
|
||||
public Integer getSenderHashcode() {
|
||||
return map.getInt("senderHashCode", 0);
|
||||
}
|
||||
|
||||
|
||||
public void setSenderHashcode(Integer senderHashcode) {
|
||||
map.putInt("senderHashcode", senderHashcode);
|
||||
}
|
||||
|
||||
|
||||
public ServiceCommand getServiceCommand() {
|
||||
return new ServiceCommand(map.getBundle("ServiceCommand"));
|
||||
}
|
||||
|
||||
|
||||
public void setServiceCommand(ServiceCommand serviceCommand) {
|
||||
map.putBundle("ServiceCommand", serviceCommand.getMap());
|
||||
this.serviceTransportType = ServiceTransportType.ServiceCommand;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasServiceCommand() {
|
||||
return (getMap().containsKey("ServiceCommand"));
|
||||
}
|
||||
|
||||
|
||||
public String getTransportType() {
|
||||
return map.getString("transportType", "unknown");
|
||||
}
|
||||
|
||||
|
||||
// On remote end, this will be converted to the "action" of a local Intent,
|
||||
// so can be used for separating types of messages to different internal handlers.
|
||||
public void setTransportType(String transportType) {
|
||||
map.putString("transportType", transportType);
|
||||
}
|
||||
|
||||
|
||||
public ServiceResult getServiceResult() {
|
||||
return new ServiceResult(map.getBundle("ServiceResult"));
|
||||
}
|
||||
|
||||
|
||||
public void setServiceResult(ServiceResult serviceResult) {
|
||||
map.putBundle("ServiceResult", serviceResult.getMap());
|
||||
this.serviceTransportType = ServiceTransportType.ServiceResult;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasServiceResult() {
|
||||
return (getMap().containsKey("ServiceResult"));
|
||||
}
|
||||
|
||||
|
||||
public ServiceNotification getServiceNotification() {
|
||||
return new ServiceNotification(map.getBundle("ServiceNotification"));
|
||||
}
|
||||
|
||||
|
||||
public void setServiceNotification(ServiceNotification notification) {
|
||||
map.putBundle("ServiceNotification", notification.getMap());
|
||||
this.serviceTransportType = ServiceTransportType.ServiceNotification;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasServiceNotification() {
|
||||
return (map.containsKey("ServiceNotification"));
|
||||
}
|
||||
|
||||
|
||||
public boolean commandDidCompleteOK() {
|
||||
return getServiceResult().resultIsOK();
|
||||
}
|
||||
|
||||
|
||||
public String getOriginalCommandName() {
|
||||
return getServiceCommand().getCommandName();
|
||||
}
|
||||
|
||||
|
||||
public String describeContentsShort() {
|
||||
String rval = "";
|
||||
rval += getTransportType();
|
||||
|
||||
if (this.serviceTransportType == ServiceTransportType.ServiceNotification) {
|
||||
rval += "note: " + getServiceNotification().getNotificationType();
|
||||
} else if (this.serviceTransportType == ServiceTransportType.ServiceCommand) {
|
||||
rval += ", cmd=" + getOriginalCommandName();
|
||||
} else if (this.serviceTransportType == ServiceTransportType.ServiceResult) {
|
||||
rval += ", cmd=" + getOriginalCommandName();
|
||||
rval += ", rslt=" + getServiceResult().getResult();
|
||||
rval += ", err=" + getServiceResult().getErrorDescription();
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
public ServiceTransport clone() {
|
||||
Parcel p = Parcel.obtain();
|
||||
Parcel p2 = Parcel.obtain();
|
||||
getMap().writeToParcel(p, 0);
|
||||
byte[] bytes = p.marshall();
|
||||
p2.unmarshall(bytes, 0, bytes.length);
|
||||
p2.setDataPosition(0);
|
||||
Bundle b = p2.readBundle();
|
||||
ServiceTransport rval = new ServiceTransport();
|
||||
rval.setMap(b);
|
||||
return rval;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data;
|
||||
|
||||
/**
|
||||
* Created by andy on 31/05/18.
|
||||
*/
|
||||
|
||||
public enum ServiceTransportType {
|
||||
|
||||
Undefined, //
|
||||
ServiceNotification, //
|
||||
|
||||
ServiceCommand, //
|
||||
ServiceResult;
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/9/16.
|
||||
*/
|
||||
public class DiscoverGattServicesTask extends ServiceTask {
|
||||
|
||||
public boolean needToConnect = false;
|
||||
|
||||
|
||||
public DiscoverGattServicesTask() {
|
||||
}
|
||||
|
||||
|
||||
public DiscoverGattServicesTask(boolean needToConnect) {
|
||||
this.needToConnect = needToConnect;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (needToConnect)
|
||||
RileyLinkUtil.getRileyLinkBLE().connectGatt();
|
||||
|
||||
RileyLinkUtil.getRileyLinkBLE().discoverServices();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/9/16.
|
||||
* <p>
|
||||
* This class is intended to be run by the Service, for the Service. Not intended for clients to run.
|
||||
*/
|
||||
public class InitializePumpManagerTask extends ServiceTask {
|
||||
|
||||
private static final String TAG = "InitPumpManagerTask";
|
||||
private RileyLinkTargetDevice targetDevice;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
|
||||
|
||||
public InitializePumpManagerTask(RileyLinkTargetDevice targetDevice) {
|
||||
super();
|
||||
this.targetDevice = targetDevice;
|
||||
}
|
||||
|
||||
|
||||
public InitializePumpManagerTask(ServiceTransport transport) {
|
||||
super(transport);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
double lastGoodFrequency = 0.0d;
|
||||
|
||||
if (RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency==null) {
|
||||
|
||||
lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d);
|
||||
lastGoodFrequency = Math.round(lastGoodFrequency * 1000d) / 1000d;
|
||||
|
||||
RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency = lastGoodFrequency;
|
||||
|
||||
// if (RileyLinkUtil.getRileyLinkTargetFrequency() == null) {
|
||||
// String pumpFrequency = SP.getString(MedtronicConst.Prefs.PumpFrequency, null);
|
||||
// }
|
||||
} else {
|
||||
lastGoodFrequency = RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency;
|
||||
}
|
||||
|
||||
if ((lastGoodFrequency > 0.0d)
|
||||
&& RileyLinkUtil.getRileyLinkCommunicationManager().isValidFrequency(lastGoodFrequency)) {
|
||||
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkReady);
|
||||
|
||||
if (L.isEnabled(L.PUMPCOMM))
|
||||
LOG.info("Setting radio frequency to {} MHz", lastGoodFrequency);
|
||||
|
||||
RileyLinkUtil.getRileyLinkCommunicationManager().setRadioFrequencyForPump(lastGoodFrequency);
|
||||
|
||||
boolean foundThePump = RileyLinkUtil.getRileyLinkCommunicationManager().tryToConnectToDevice();
|
||||
|
||||
if (foundThePump) {
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady);
|
||||
} else {
|
||||
RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorError,
|
||||
RileyLinkError.NoContactWithDevice);
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump);
|
||||
}
|
||||
|
||||
} else {
|
||||
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/10/16.
|
||||
*/
|
||||
public class PumpTask extends ServiceTask {
|
||||
|
||||
public PumpTask() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public PumpTask(ServiceTransport transport) {
|
||||
super(transport);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicFragment;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/16/16.
|
||||
*/
|
||||
public class ResetRileyLinkConfigurationTask extends PumpTask {
|
||||
|
||||
private static final String TAG = "ResetRileyLinkTask";
|
||||
|
||||
|
||||
public ResetRileyLinkConfigurationTask() {
|
||||
}
|
||||
|
||||
|
||||
public ResetRileyLinkConfigurationTask(ServiceTransport transport) {
|
||||
super(transport);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
MedtronicFragment.refreshButtonEnabled(false);
|
||||
MedtronicPumpPlugin.isBusy = true;
|
||||
RileyLinkMedtronicService.getInstance().resetRileyLinkConfiguration();
|
||||
MedtronicPumpPlugin.isBusy = false;
|
||||
MedtronicFragment.refreshButtonEnabled(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/9/16.
|
||||
*/
|
||||
public class ServiceTask implements Runnable {
|
||||
|
||||
private static final String TAG = "ServiceTask(base)";
|
||||
public boolean completed = false;
|
||||
protected ServiceTransport mTransport;
|
||||
|
||||
|
||||
public ServiceTask() {
|
||||
init(new ServiceTransport());
|
||||
}
|
||||
|
||||
|
||||
public ServiceTask(ServiceTransport transport) {
|
||||
init(transport);
|
||||
}
|
||||
|
||||
|
||||
public void init(ServiceTransport transport) {
|
||||
mTransport = transport;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
|
||||
|
||||
public void preOp() {
|
||||
// This function is called by UI thread before running asynch thread.
|
||||
}
|
||||
|
||||
|
||||
public void postOp() {
|
||||
// This function is called by UI thread after running asynch thread.
|
||||
}
|
||||
|
||||
|
||||
public ServiceTransport getServiceTransport() {
|
||||
return mTransport;
|
||||
}
|
||||
|
||||
/*
|
||||
* protected void sendResponse(ServiceResult result) {
|
||||
* RoundtripService.getInstance().sendServiceTransportResponse(mTransport,result);
|
||||
* }
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/9/16.
|
||||
*/
|
||||
public class ServiceTaskExecutor extends ThreadPoolExecutor {
|
||||
|
||||
private static final String TAG = "ServiceTaskExecutor";
|
||||
private static ServiceTaskExecutor instance;
|
||||
private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
static {
|
||||
instance = new ServiceTaskExecutor();
|
||||
}
|
||||
|
||||
|
||||
private ServiceTaskExecutor() {
|
||||
super(1, 1, 10000, TimeUnit.MILLISECONDS, taskQueue);
|
||||
}
|
||||
|
||||
|
||||
public static ServiceTaskExecutor getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
public static ServiceTask startTask(ServiceTask task) {
|
||||
instance.execute(task); // task will be run on async thread from pool.
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
// FIXME
|
||||
protected void beforeExecute(Thread t, Runnable r) {
|
||||
// This is run on either caller UI thread or Service UI thread.
|
||||
ServiceTask task = (ServiceTask)r;
|
||||
Log.v(TAG, "About to run task " + task.getClass().getSimpleName());
|
||||
RileyLinkUtil.setCurrentTask(task);
|
||||
task.preOp();
|
||||
}
|
||||
|
||||
|
||||
// FIXME
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
// This is run on either caller UI thread or Service UI thread.
|
||||
ServiceTask task = (ServiceTask)r;
|
||||
task.postOp();
|
||||
Log.v(TAG, "Finishing task " + task.getClass().getSimpleName());
|
||||
RileyLinkUtil.finishCurrentTask(task);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks;
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicFragment;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
||||
import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService;
|
||||
|
||||
/**
|
||||
* Created by geoff on 7/16/16.
|
||||
*/
|
||||
public class WakeAndTuneTask extends PumpTask {
|
||||
|
||||
private static final String TAG = "WakeAndTuneTask";
|
||||
|
||||
|
||||
public WakeAndTuneTask() {
|
||||
}
|
||||
|
||||
|
||||
public WakeAndTuneTask(ServiceTransport transport) {
|
||||
super(transport);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
MedtronicFragment.refreshButtonEnabled(false);
|
||||
MedtronicPumpPlugin.isBusy = true;
|
||||
RileyLinkMedtronicService.getInstance().doTuneUpDevice();
|
||||
MedtronicPumpPlugin.isBusy = false;
|
||||
MedtronicFragment.refreshButtonEnabled(true);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue