- copied roundtrip2 project into here (for easier use), some of it might be reused

- added PumpCommon
- added PumpMedtronic
This commit is contained in:
Andy Rozman 2018-05-01 22:44:53 +01:00
parent 99544e1f2b
commit 4b121ae892
196 changed files with 15335 additions and 78 deletions

View file

@ -2,17 +2,25 @@ buildscript {
repositories {
maven { url 'https://maven.fabric.io/public' }
jcenter()
google()
maven {
url 'http://oss.jfrog.org/artifactory/oss-snapshot-local'
}
}
dependencies {
classpath 'io.fabric.tools:gradle:1.+'
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2'
//classpath 'io.realm:realm-gradle-plugin:1.1.1'
classpath 'io.realm:realm-gradle-plugin:4.0.0'
}
}
apply plugin: "com.android.application"
apply plugin: "io.fabric"
apply plugin: "jacoco-android"
apply plugin: 'com.jakewharton.butterknife'
apply plugin: 'realm-android'
ext {
supportLibraryVersion = "27.0.2"

View file

@ -0,0 +1,109 @@
package com.gxwtech.roundtrip2;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View file

@ -0,0 +1,21 @@
package com.gxwtech.roundtrip2;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.gxwtech.roundtrip2.CommunicationService.CommunicationService;
/**
* Created by Tim on 07/06/2016.
* Receives BOOT_COMPLETED Intent and starts service
*/
public class AutoStart extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, CommunicationService.class));
}
}

View file

@ -0,0 +1,279 @@
package com.gxwtech.roundtrip2.CommunicationService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.gxwtech.roundtrip2.CommunicationService.Objects.Bolus;
import com.gxwtech.roundtrip2.CommunicationService.Objects.DateDeserializer;
import com.gxwtech.roundtrip2.CommunicationService.Objects.Integration;
import com.gxwtech.roundtrip2.CommunicationService.Objects.IntegrationSerializer;
import com.gxwtech.roundtrip2.CommunicationService.Objects.RealmManager;
import com.gxwtech.roundtrip2.CommunicationService.Objects.TempBasal;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.util.Check;
import java.util.Date;
import java.util.List;
import info.nightscout.androidaps.MainApp;
/**
* Created by Tim on 07/06/2016.
* This service listens out for requests from HAPP and processes them
*/
public class CommunicationService extends android.app.Service {
public CommunicationService(){}
final static String TAG = "CommunicationService";
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String action = "";
Long requested = 0L;
String safteyCheck = "";
String pump = "";
List<Integration> remoteIntegrations;
List<String> remoteTreatments;
Bundle data = new Bundle();
RealmManager realmManager = new RealmManager();
Log.d(TAG, "START");
try {
/*
Expected Bundle data...
"ACTION" - What is this incoming request? Example: "NEW_TREATMENTS"
"DATE_REQUESTED" - When was this requested? So we can ignore old requests
"PUMP" - Name of the pump the APS expects this app to support
"INTEGRATION_OBJECTS" - Array of Integration Objects, details of the objects being synced. *OPTIONAL for NEW_TREATMENTS only*
"TREATMENT_OBJECTS" - Array of Objects themselves being synced, TempBasal or Bolus *OPTIONAL for NEW_TREATMENTS only*
*/
data = msg.getData();
action = data.getString(RT2Const.commService.ACTION);
requested = data.getLong(RT2Const.commService.DATE_REQUESTED, 0);
pump = data.getString(RT2Const.commService.PUMP);
Log.d("RECEIVED: ACTION", action);
Log.d("RECEIVED: DATE", requested.toString());
Log.d("RECEIVED: PUMP", pump);
} catch (Exception e) {
e.printStackTrace();
// TODO: 16/01/2016 Issue getting treatment details from APS app msg for user
}
switch (action) {
case RT2Const.commService.INCOMING_TEST_MSG:
Resources appR = MainApp.instance().getResources();
CharSequence txt = appR.getText(appR.getIdentifier("app_name", "string", MainApp.instance().getPackageName()));
Toast.makeText(MainApp.instance(), txt + ": Pump Driver App has connected successfully. ", Toast.LENGTH_LONG).show();
Log.d(TAG, txt + ": APS app has connected successfully.");
break;
case RT2Const.commService.INCOMING_NEW_TREATMENTS:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
Gson gson = gsonBuilder.create();
remoteIntegrations = gson.fromJson(data.getString(RT2Const.commService.INTEGRATION_OBJECTS), new TypeToken<List<Integration>>() {}.getType());
remoteTreatments = gson.fromJson(data.getString(RT2Const.commService.TREATMENT_OBJECTS), new TypeToken<List<String>>() {}.getType());
Log.d("RECEIVED: INTEGRATIONS", remoteIntegrations.toString());
Log.d("RECEIVED: TREATMENTS", remoteTreatments.toString());
for (int i = 0; i < remoteIntegrations.size(); i++) {
realmManager.getRealm().beginTransaction();
Integration integrationForAPS = remoteIntegrations.get(i);
integrationForAPS.setType ("aps_app");
integrationForAPS.setState ("received");
integrationForAPS.setToSync (true);
integrationForAPS.setDate_updated (new Date());
integrationForAPS.setRemote_id(remoteIntegrations.get(i).getLocal_object_id());
Integration integrationForPump = new Integration();
integrationForPump.setType ("pump");
integrationForPump.setDate_updated (new Date());
integrationForPump.setLocal_object(remoteIntegrations.get(i).getLocal_object());
String localObjectlID = "", localObjectState = "", localObjectDetails = "", rejectRequest = "";
if (!Check.isPumpSupported(pump))
rejectRequest += "Pump requested not supported. ";
if (Check.isRequestTooOld(requested)) rejectRequest += "Request too old. ";
switch (remoteIntegrations.get(i).getLocal_object()) {
case "temp_basal":
TempBasal tempBasal = gson.fromJson(remoteTreatments.get(i), TempBasal.class);
realmManager.getRealm().copyToRealm(tempBasal);
localObjectlID = tempBasal.getId();
switch (remoteIntegrations.get(i).getAction()) {
case "new":
rejectRequest += Check.isNewTempBasalSafe(tempBasal);
if (rejectRequest.equals("")) // TODO: 12/08/2016 command to send TempBasal to pump
break;
case "cancel":
rejectRequest += Check.isCancelTempBasalSafe(tempBasal, integrationForAPS, realmManager.getRealm());
if (rejectRequest.equals("")) // TODO: 12/08/2016 command to send TempBasal to pump
break;
}
break;
case "bolus_delivery":
Bolus bolus = gson.fromJson(remoteTreatments.get(i), Bolus.class);
realmManager.getRealm().copyToRealm(bolus);
localObjectlID = bolus.getId();
rejectRequest += Check.isBolusSafeToAction(bolus);
if (rejectRequest.equals("")) //TODO: 12/08/2016 command to action Bolus
break;
}
if (rejectRequest.equals("")) {
//all ok
localObjectState = "received";
localObjectDetails = "Request sent to pump";
} else {
//reject
localObjectState = "error";
localObjectDetails = rejectRequest;
}
integrationForAPS.setLocal_object_id(localObjectlID);
integrationForAPS.setState(localObjectState);
integrationForAPS.setDetails(localObjectDetails);
realmManager.getRealm().copyToRealm(integrationForAPS);
integrationForPump.setLocal_object_id(localObjectlID);
integrationForPump.setState(localObjectState);
integrationForPump.setDetails(localObjectDetails);
realmManager.getRealm().copyToRealm(integrationForPump);
realmManager.getRealm().commitTransaction();
}
break;
default:
Log.e(TAG, "handleMessage: Unknown Action: " + action);
}
connect_to_aps_app();
realmManager.closeRealm();
}
}
final Messenger myMessenger = new Messenger(new IncomingHandler());
@Override
public IBinder onBind(Intent intent) {
return myMessenger.getBinder();
}
public void updateAPSApp(){
RealmManager realmManager = new RealmManager();
List<Integration> integrations = Integration.getIntegrationsToSync("aps_app", null, realmManager.getRealm());
if (integrations.size() > 0) {
/*
Bundle data...
"ACTION" - What is this incoming request? Example: "TREATMENT_UPDATES"
"INTEGRATION_OBJECTS" - Array of Integration Objects, details of the objects being synced. *OPTIONAL for UPDATE_TREATMENTS only*
*/
Log.d(TAG, "UPDATE APS App:" + integrations.size() + " treatments to update");
Log.d(TAG, "INTEGRATIONS:" + integrations.toString());
Message msg = Message.obtain();
boolean updateOK = true;
try {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Class.forName("io.realm.IntegrationRealmProxy"), new IntegrationSerializer())
.create();
Bundle bundle = new Bundle();
bundle.putString(RT2Const.commService.ACTION, RT2Const.commService.OUTGOING_TREATMENT_UPDATES);
bundle.putString(RT2Const.commService.INTEGRATION_OBJECTS, gson.toJson(integrations));
msg.setData(bundle);
} catch (ClassNotFoundException e){
updateOK = false;
Log.e(TAG, "Error creating gson object: " + e.getLocalizedMessage());
}
try {
myService.send(msg);
Log.d(TAG, integrations.size() + " updates sent");
} catch (RemoteException e) {
updateOK = false;
Log.e(TAG, integrations.size() + " updates failed. " + e.getLocalizedMessage());
}
for (Integration integration : integrations){
realmManager.getRealm().beginTransaction();
if (updateOK) {
integration.setState("sent");
} else {
integration.setState("error");
integration.setDetails("Update to APS failed. Will not be resent.");
}
integration.setToSync(false);
realmManager.getRealm().commitTransaction();
}
}
try {
if (isBound) CommunicationService.this.unbindService(myConnection);
} catch (IllegalArgumentException e) {
//catch if service was killed in a unclean way
}
realmManager.closeRealm();
}
//Connect to the APS App Treatments Service
private void connect_to_aps_app(){
// TODO: 16/06/2016 should be able to pick the APS app from UI not hardcoded
Intent intent = new Intent("com.hypodiabetic.happ.services.TreatmentService");
intent.setPackage("com.hypodiabetic.happ");
CommunicationService.this.bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
}
//Our Service that APS App will connect to
private Messenger myService = null;
private Boolean isBound = false;
private ServiceConnection myConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
myService = new Messenger(service);
isBound = true;
updateAPSApp();
}
public void onServiceDisconnected(ComponentName className) {
myService = null;
isBound = false;
}
};
}

View file

@ -0,0 +1,91 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
import io.realm.Sort;
/**
* Created by Tim on 05/08/2016.
*/
public class Bolus extends RealmObject {
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public Date getTimestamp() {
return timestamp;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getId() {
return id;
}
private String id = UUID.randomUUID().toString();
private Date timestamp = new Date();
private String type;
private Double value;
public static Bolus getBolus(String uuid, Realm realm) {
RealmResults<Bolus> results = realm.where(Bolus.class)
.equalTo("id", uuid)
.findAllSorted("timestamp", Sort.DESCENDING);
if (results.isEmpty()) {
return null;
} else {
return results.first();
}
}
public static List<Bolus> getBolusList(Realm realm){
RealmResults<Bolus> results = realm.where(Bolus.class)
.findAllSorted("timestamp", Sort.DESCENDING);
if (results.isEmpty()) {
return null;
} else {
return results;
}
}
public static List<Bolus> getBolusesBetween(Date dateFrom, Date dateTo, Realm realm) {
RealmResults<Bolus> results = realm.where(Bolus.class)
.greaterThanOrEqualTo("timestamp", dateFrom)
.lessThanOrEqualTo("timestamp", dateTo)
.findAllSorted("timestamp", Sort.DESCENDING);
return results;
}
public static Double getBolusCountBetween(Date dateFrom, Date dateTo, Realm realm) {
Number result = realm.where(Bolus.class)
.greaterThanOrEqualTo("timestamp", dateFrom)
.lessThanOrEqualTo("timestamp", dateTo)
.sum("value");
return result.doubleValue();
}
public static class sortByDateTimeOld2YoungOLD implements Comparator<Bolus> {
@Override
public int compare(Bolus o1, Bolus o2) {
return o2.getTimestamp().compareTo(o1.getTimestamp());
}
}
}

View file

@ -0,0 +1,20 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import java.lang.reflect.Type;
import java.util.Date;
/**
* Created by Tim on 16/08/2016.
* Used by GSON to Deserializer Dates
*/
public class DateDeserializer implements JsonDeserializer<Date> {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
Date date = null;
date = new Date(json.getAsJsonPrimitive().getAsLong());
return date;
}
}

View file

@ -0,0 +1,216 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
import io.realm.Sort;
/**
* Created by Tim on 16/01/2016.
* This table holds Integration details of an object
* one object may have multiple Integrations
*/
public class Integration extends RealmObject {
public String getId() {
return id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Date getTimestamp() {
return timestamp;
}
public Date getDate_updated() {
return date_updated;
}
public void setDate_updated(Date date_updated) {
this.date_updated = date_updated;
}
public String getLocal_object() {
return local_object;
}
public void setLocal_object(String local_object) {
this.local_object = local_object;
}
public String getLocal_object_id() {
return local_object_id;
}
public void setLocal_object_id(String local_object_id) {
this.local_object_id = local_object_id;
}
public String getRemote_id() {
return remote_id;
}
public void setRemote_id(String remote_id) {
this.remote_id = remote_id;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public String getRemote_var1() {
return remote_var1;
}
public void setRemote_var1(String remote_var1) {
this.remote_var1 = remote_var1;
}
public String getAuth_code() {
return auth_code;
}
public void setAuth_code(String auth_code) {
this.auth_code = auth_code;
}
public Boolean getToSync() {
return toSync;
}
public void setToSync(Boolean toSync) {
this.toSync = toSync;
}
private String id;
private String type; //What Integration is this?
private String state; //Current state this Integration is in
private String action; //Requested action for this object
private Date timestamp; //Date created
private Date date_updated; //Last time the Integration for this object was updated
private String local_object; //What rt2 object is this? Bolus, TempBasal etc
private String local_object_id; //HAPP ID for this object
private String remote_id; //ID provided by the remote system
private String details; //The details of this Integration attempt
private String remote_var1; //Misc information about this Integration
private String auth_code; //auth_code if required
private Boolean toSync; //Do we need to sync this object?
public Integration(){
id = UUID.randomUUID().toString();
timestamp = new Date();
date_updated = new Date();
remote_var1 = "";
state = "";
toSync = true;
}
public Integration(String type, String local_object, String local_object_id){
id = UUID.randomUUID().toString();
timestamp = new Date();
date_updated = new Date();
remote_var1 = "";
state = "";
this.type = type;
this.local_object = local_object;
this.local_object_id = local_object_id;
toSync = true;
}
public static Integration getIntegration(String type, String local_object, String rt2_id, Realm realm){
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("type", type)
.equalTo("local_object", local_object)
.equalTo("local_object_id", rt2_id)
.findAllSorted("date_updated", Sort.DESCENDING);
if (results.isEmpty()) { //We dont have an Integration for this item
return null;
} else { //Found an Integration, return it
return results.first();
}
}
public static List<Integration> getIntegrationsFor(String local_object, String local_object_id, Realm realm) {
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("local_object", local_object)
.equalTo("local_object_id", local_object_id)
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
}
public static List<Integration> getIntegrationsHoursOld(String type, String local_object, int inLastHours, Realm realm) {
Date now = new Date();
Date hoursAgo = new Date(now.getTime() - (inLastHours * 60 * 60 * 1000));
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("local_object", local_object)
.equalTo("type", type)
.greaterThanOrEqualTo("date_updated", hoursAgo)
.lessThanOrEqualTo("date_updated", now)
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
}
public static List<Integration> getIntegrationsToSync(String type, String local_object, Realm realm) {
if (local_object != null) {
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("local_object", local_object)
.equalTo("type", type)
.equalTo("toSync", Boolean.TRUE)
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
} else {
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("type", type)
.equalTo("toSync", Boolean.TRUE)
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
}
}
public static Integration getIntegrationByID(String uuid, Realm realm) {
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("id", uuid)
.findAllSorted("timestamp", Sort.DESCENDING);
if (results.isEmpty()) {
return null;
} else {
return results.first();
}
}
public static List<Integration> getUpdatedInLastMins(Integer inLastMins, String type, Realm realm) {
Date now = new Date();
Date minsAgo = new Date(now.getTime() - (inLastMins * 60 * 1000));
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("type", type)
.greaterThanOrEqualTo("date_updated", minsAgo)
.lessThanOrEqualTo("date_updated", now)
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
}
public static List<Integration> getIntegrationsWithErrors(String type, Realm realm) {
RealmResults<Integration> results = realm.where(Integration.class)
.equalTo("type", type)
.equalTo("state", "error")
.findAllSorted("date_updated", Sort.DESCENDING);
return results;
}
}

View file

@ -0,0 +1,35 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
/**
* Created by Tim on 12/08/2016.
* Required by Realm for converting to gson https://realm.io/docs/java/latest/#gson
*/
public class IntegrationSerializer implements JsonSerializer<Integration> {
@Override
public JsonElement serialize(Integration src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", src.getId());
jsonObject.addProperty("type", src.getType());
jsonObject.addProperty("state", src.getState());
jsonObject.addProperty("action", src.getAction());
jsonObject.addProperty("timestamp", src.getTimestamp().getTime());
jsonObject.addProperty("date_updated", src.getDate_updated().getTime());
jsonObject.addProperty("local_object", src.getLocal_object());
jsonObject.addProperty("local_object_id", src.getLocal_object_id());
jsonObject.addProperty("remote_id", src.getRemote_id());
jsonObject.addProperty("details", src.getDetails());
jsonObject.addProperty("remote_var1", src.getRemote_var1());
jsonObject.addProperty("auth_code", src.getAuth_code());
jsonObject.addProperty("toSync", src.getToSync());
return jsonObject;
}
}

View file

@ -0,0 +1,23 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import io.realm.Realm;
/**
* Created by Tim on 11/08/2016.
*/
public class RealmManager {
private Realm realm;
public RealmManager(){
realm = Realm.getDefaultInstance();
}
public void closeRealm(){
realm.close();
}
public Realm getRealm(){
if (realm.isClosed() || realm.isEmpty()) realm = Realm.getDefaultInstance();
return realm;
}
}

View file

@ -0,0 +1,179 @@
package com.gxwtech.roundtrip2.CommunicationService.Objects;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
import io.realm.Sort;
import io.realm.annotations.Ignore;
/**
* Created by Tim on 03/09/2015.
*/
public class TempBasal extends RealmObject {
public Double getRate() {
return rate;
}
public void setRate(Double rate) {
this.rate = rate;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public Date getStart_time() {
return start_time;
}
public void setStart_time(Date start_time) {
this.start_time = start_time;
}
public String getBasal_adjustemnt() {
return basal_adjustemnt;
}
public void setBasal_adjustemnt(String basal_adjustemnt) {
this.basal_adjustemnt = basal_adjustemnt;
}
public String getAps_mode() {
return aps_mode;
}
public void setAps_mode(String aps_mode) {
this.aps_mode = aps_mode;
}
public String getId() {
return id;
}
public Date getTimestamp() {
return timestamp;
}
private String id = UUID.randomUUID().toString();
private Double rate = 0D; //Temp Basal Rate for (U/hr) mode
private Integer duration = 0; //Duration of Temp
private Date start_time; //When the Temp Basal started
private String basal_adjustemnt = ""; //High or Low temp
private String aps_mode;
@Ignore
public Date timestamp = new Date();
public static TempBasal getTempBasalByID(String uuid, Realm realm) {
RealmResults<TempBasal> results = realm.where(TempBasal.class)
.equalTo("id", uuid)
.findAllSorted("start_time", Sort.DESCENDING);
if (results.isEmpty()) {
return null;
} else {
return results.first();
}
}
public static TempBasal last(Realm realm) {
RealmResults<TempBasal> results = realm.where(TempBasal.class)
.findAllSorted("start_time", Sort.DESCENDING);
if (results.isEmpty()) {
return new TempBasal(); //returns an empty TempBasal, other than null
} else {
return results.first();
}
}
public static TempBasal lastActive(Realm realm) {
RealmResults<TempBasal> results = realm.where(TempBasal.class)
.findAllSorted("start_time", Sort.DESCENDING);
if (results.isEmpty()) {
return null;
} else {
TempBasal tempBasal = results.first();
Integration integration = Integration.getIntegration("pump","temp_Basal",tempBasal.getId(),realm);
if (integration.getState().equals("set")){
return tempBasal;
} else {
return null;
}
}
}
public static TempBasal getCurrentActive(Date atThisDate, Realm realm) {
RealmResults<TempBasal> results = realm.where(TempBasal.class)
.findAllSorted("start_time", Sort.DESCENDING);
TempBasal last = null;
if (!results.isEmpty()) last = results.first();
if (last != null && last.isactive(atThisDate)){
return last;
} else {
return new TempBasal(); //returns an empty TempBasal, other than null or inactive basal
}
}
public boolean isactive(Date atThisDate){
if (atThisDate == null) atThisDate = new Date();
if (start_time == null){ return false;}
Date fur = new Date(start_time.getTime() + duration * 60000);
if (fur.after(atThisDate)){
return true;
} else {
return false;
}
}
public String ageFormattted(){
Integer minsOld = age();
if (minsOld > 1){
return minsOld + " mins ago";
} else {
return minsOld + " min ago";
}
}
public int age(){
Date timeNow = new Date();
return (int)(timeNow.getTime() - timestamp.getTime()) /1000/60; //Age in Mins the Temp Basal was suggested
}
public Date endDate(){
Date endedAt = new Date(start_time.getTime() + (duration * 1000 * 60)); //The date this Temp Basal ended
return endedAt;
}
public Long durationLeft(){
if (start_time != null) {
Date timeNow = new Date();
Long min_left = ((start_time.getTime() + duration * 60000) - timeNow.getTime()) / 60000; //Time left to run in Mins
return min_left;
} else {
return duration.longValue();
}
}
public static List<TempBasal> getTempBasalsDated(Date dateFrom, Date dateTo, Realm realm) {
RealmResults<TempBasal> results = realm.where(TempBasal.class)
.greaterThanOrEqualTo("start_time", dateFrom)
.lessThanOrEqualTo("start_time", dateTo)
.findAllSorted("start_time", Sort.DESCENDING);
return results;
}
public boolean checkIsCancelRequest() {
if (rate.equals(0D) && duration.equals(0)){
return true;
} else {
return false;
}
}
}

View file

@ -0,0 +1,83 @@
package com.gxwtech.roundtrip2.HistoryActivity;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import com.gxwtech.roundtrip2.R;
/**
* An activity representing a single HistoryPage detail screen. This
* activity is only used narrow width devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a {@link HistoryPageListActivity}.
*/
public class HistoryPageDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_historypage_detail);
Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
// Show the Up button in the action bar.
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(HistoryPageDetailFragment.ARG_ITEM_ID,
getIntent().getStringExtra(HistoryPageDetailFragment.ARG_ITEM_ID));
HistoryPageDetailFragment fragment = new HistoryPageDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.historypage_detail_container, fragment)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
navigateUpTo(new Intent(this, HistoryPageListActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -0,0 +1,70 @@
package com.gxwtech.roundtrip2.HistoryActivity;
import android.app.Activity;
import android.os.Bundle;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.gxwtech.roundtrip2.R;
/**
* A fragment representing a single HistoryPage detail screen.
* This fragment is either contained in a {@link HistoryPageListActivity}
* in two-pane mode (on tablets) or a {@link HistoryPageDetailActivity}
* on handsets.
*/
public class HistoryPageDetailFragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
/**
* The dummy content this fragment is presenting.
*/
private HistoryPageListContent.RecordHolder mItem;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public HistoryPageDetailFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM_ID)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
mItem = HistoryPageListContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
Activity activity = this.getActivity();
CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout);
if (appBarLayout != null) {
appBarLayout.setTitle("Details"/*mItem.content*/);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.historypage_detail, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
((TextView) rootView.findViewById(R.id.historypage_detail)).setText(mItem.details);
}
return rootView;
}
}

View file

@ -0,0 +1,230 @@
package com.gxwtech.roundtrip2.HistoryActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
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.TextView;
import com.gxwtech.roundtrip2.R;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* An activity representing a list of HistoryPages. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a {@link HistoryPageDetailActivity} representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
*/
public class HistoryPageListActivity extends AppCompatActivity {
private static final String TAG = "HistoryPageListActivity";
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private BroadcastReceiver mBroadcastRecevier;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_historypage_list);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle(R.string.title_pump_history);
// Show the Up button in the action bar.
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
View recyclerView = findViewById(R.id.historypage_list);
assert recyclerView != null;
setupRecyclerView((RecyclerView) recyclerView);
if (findViewById(R.id.historypage_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
mBroadcastRecevier = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent receivedIntent) {
if (receivedIntent == null) {
Log.e(TAG,"onReceive: received null intent");
} else {
String action = receivedIntent.getAction();
if (action == null) {
Log.e(TAG, "onReceive: null action");
} else {
if (RT2Const.local.INTENT_historyPageBundleIncoming.equals(action)) {
Bundle incomingBundle = receivedIntent.getExtras().getBundle(RT2Const.IPC.MSG_PUMP_history_key);
ServiceTransport transport = new ServiceTransport(incomingBundle);
ServiceResult result = transport.getServiceResult();
if ("RetrieveHistoryPageResult".equals(result.getServiceResultType())) {
RetrieveHistoryPageResult pageResult = (RetrieveHistoryPageResult) result;
Bundle page = pageResult.getPageBundle();
ArrayList<Bundle> recordBundleList = page.getParcelableArrayList("mRecordList");
try {
for (Bundle record : recordBundleList) {
HistoryPageListContent.addItem(record);
}
} catch (java.lang.NullPointerException e) {
e.printStackTrace();
}
}
} else {
Log.e(TAG,"Unrecognized intent action: "+action);
}
}
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(RT2Const.local.INTENT_historyPageBundleIncoming);
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(mBroadcastRecevier,filter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_bluetooth_scan, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.miScan:
getHistory();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void getHistory(){
// tell them we're ready for data
Intent intent = new Intent(RT2Const.local.INTENT_historyPageViewerReady);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
}
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(HistoryPageListContent.ITEMS));
}
public class SimpleItemRecyclerViewAdapter
extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> {
private final List<HistoryPageListContent.RecordHolder> mValues;
public SimpleItemRecyclerViewAdapter(List<HistoryPageListContent.RecordHolder> items) {
mValues = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.historypage_list_content, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mIdView.setText(mValues.get(position).dateAndName);
String keytext = "";
Set<String> keys = holder.mItem.content.keySet();
int n = 0;
for (String key : keys) {
if (!key.equals("_type") && !key.equals("_stype") && !key.equals("timestamp") && !key.equals("_opcode")) {
try {
keytext += key + ":" + holder.mItem.content.get(key).toString();
n++;
if (n < keys.size() - 1) {
keytext += "\n";
}
} catch (NullPointerException e) {
e.printStackTrace();
}
}
}
holder.mContentView.setText(keytext);
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mTwoPane) {
Bundle arguments = new Bundle();
arguments.putString(HistoryPageDetailFragment.ARG_ITEM_ID, holder.mItem.id);
HistoryPageDetailFragment fragment = new HistoryPageDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.historypage_detail_container, fragment)
.commit();
} else {
Context context = v.getContext();
Intent intent = new Intent(context, HistoryPageDetailActivity.class);
intent.putExtra(HistoryPageDetailFragment.ARG_ITEM_ID, holder.mItem.id);
context.startActivity(intent);
}
}
});
}
@Override
public int getItemCount() {
return mValues.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final TextView mIdView;
public final TextView mContentView;
public HistoryPageListContent.RecordHolder mItem;
public ViewHolder(View view) {
super(view);
mView = view;
mIdView = (TextView) view.findViewById(R.id.id);
mContentView = (TextView) view.findViewById(R.id.content);
}
@Override
public String toString() {
return super.toString() + " '" + mContentView.getText() + "'";
}
}
}
}

View file

@ -0,0 +1,94 @@
package com.gxwtech.roundtrip2.HistoryActivity;
import android.os.Bundle;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Created by geoff on 6/12/16.
*/
public class HistoryPageListContent {
public static final List<RecordHolder> ITEMS = new ArrayList<>();
/**
* A map of items, by ID.
*/
public static final Map<String, RecordHolder> ITEM_MAP = new HashMap<>();
static void addItem(Bundle recordBundle) {
addItem(new RecordHolder(recordBundle));
}
private static void addItem(RecordHolder item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
private static String makeDetails(int position) {
RecordHolder rh = ITEMS.get(position);
if (rh == null) {
return "(null)";
}
return makeDetails(rh.content);
}
private static String makeDetails(Bundle historyEntry) {
Set<String> ignoredSet = new HashSet<>();
ignoredSet.add("_type");
ignoredSet.add("_stype");
ignoredSet.add("_opcode");
ignoredSet.add("timestamp");
StringBuilder builder = new StringBuilder();
int n = 0;
for (String key : historyEntry.keySet()) {
if (!ignoredSet.contains(key)) {
builder.append(key);
n++;
if (n<historyEntry.keySet().size()-1) {
builder.append("\n");
}
}
}
return builder.toString();
}
public static class RecordHolder {
public final String id;
public final String dateAndName;
public final Bundle content;
public final String details;
public RecordHolder(Bundle content) {
id = String.format("%d",content.hashCode());
String rawTimestamp = content.getString("timestamp","0000-00-00T00:00:00");
int tspot = rawTimestamp.indexOf('T');
StringBuilder dateAndNameBuilder = new StringBuilder();
dateAndNameBuilder.append(rawTimestamp.substring(0,tspot-1));
dateAndNameBuilder.append("\n");
dateAndNameBuilder.append(rawTimestamp.substring(tspot+1,rawTimestamp.length()-1));
dateAndNameBuilder.append("\n");
String veryShortName = content.getString("_stype","(??????)");
if (veryShortName.length() >= 12) {
veryShortName = veryShortName.substring(0,12);
}
dateAndNameBuilder.append(veryShortName);
this.dateAndName = dateAndNameBuilder.toString();
this.content = content;
details = makeDetails(content);
}
@Override
public String toString() {
return content.getString("_stype", "(unk)");
}
}
}

View file

@ -0,0 +1,614 @@
package com.gxwtech.roundtrip2;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.preference.PreferenceManager;
import android.content.SharedPreferences;
import com.gxwtech.roundtrip2.HistoryActivity.HistoryPageListActivity;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ListView;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.ServiceData.BasalProfile;
import com.gxwtech.roundtrip2.ServiceData.BolusWizardCarbProfile;
import com.gxwtech.roundtrip2.ServiceData.ISFProfile;
import com.gxwtech.roundtrip2.ServiceData.PumpModelResult;
import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceClientActions;
import com.gxwtech.roundtrip2.ServiceData.ServiceCommand;
import com.gxwtech.roundtrip2.ServiceData.ServiceNotification;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
import com.gxwtech.roundtrip2.ServiceMessageViewActivity.ServiceMessageViewListActivity;
import com.gxwtech.roundtrip2.util.tools;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final int REQUEST_ENABLE_BT = 2177; // just something unique.
private RoundtripServiceClientConnection roundtripServiceClientConnection;
private BroadcastReceiver mBroadcastReceiver;
BroadcastReceiver apsAppConnected;
Bundle storeForHistoryViewer;
//UI items
private DrawerLayout mDrawerLayout;
private LinearLayout mDrawerLinear;
private Toolbar toolbar;
public static Context mContext; // TODO: 09/07/2016 @TIM this should not be needed
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.w(TAG,"onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupMenuAndToolbar();
mContext = this; // TODO: 09/07/2016 @TIM this should not be needed
//Sets default Preferences
PreferenceManager.setDefaultValues(this, R.xml.pref_pump, false);
PreferenceManager.setDefaultValues(this, R.xml.pref_rileylink, false);
setBroadcastReceiver();
/* start the RoundtripService */
/* using startService() will keep the service running until it is explicitly stopped
* with stopService() or by RoundtripService calling stopSelf().
* Note that calling startService repeatedly has no ill effects on RoundtripService
*/
// explicitly call startService to keep it running even when the GUI goes away.
Intent bindIntent = new Intent(this,RoundtripService.class);
startService(bindIntent);
linearProgressBar = (ProgressBar)findViewById(R.id.progressBarCommandActivity);
spinnyProgressBar = (ProgressBar)findViewById(R.id.progressBarSpinny);
}
@Override
protected void onResume(){
super.onResume();
setBroadcastReceiver();
}
@Override
public void onPause() {
super.onPause();
if (apsAppConnected != null){
LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(apsAppConnected);
}
if (mBroadcastReceiver != null){
LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(mBroadcastReceiver);
}
}
public void setBroadcastReceiver() {
//Register this receiver for UI Updates
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent receivedIntent) {
if (receivedIntent == null) {
Log.e(TAG, "onReceive: received null intent");
} else {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainApp.instance());
ServiceTransport transport;
switch (receivedIntent.getAction()) {
case RT2Const.local.INTENT_serviceConnected:
case RT2Const.local.INTENT_NEW_rileylinkAddressKey:
showIdle();
/**
* Client MUST send a "UseThisRileylink" message because it asserts that
* the user has given explicit permission to use bluetooth.
*
* We can change the format so that it is a simple "bluetooth OK" message,
* rather than an explicit address of a Rileylink, and the Service can
* use the last known good value. But the kick-off of bluetooth ops must
* come from an Activity.
*/
String RileylinkBLEAddress = prefs.getString(RT2Const.serviceLocal.rileylinkAddressKey, "");
if (RileylinkBLEAddress.equals("")){
// TODO: 11/07/2016 @TIM UI message for user
Log.e(TAG, "No Rileylink BLE Address saved in app");
} else {
showBusy("Configuring Service", 50);
MainApp.getServiceClientConnection().setThisRileylink(RileylinkBLEAddress);
}
break;
case RT2Const.local.INTENT_NEW_pumpIDKey:
MainApp.getServiceClientConnection().sendPUMP_useThisDevice(prefs.getString(RT2Const.serviceLocal.pumpIDKey, ""));
break;
case RT2Const.local.INTENT_historyPageViewerReady:
Intent sendHistoryIntent = new Intent(RT2Const.local.INTENT_historyPageBundleIncoming);
sendHistoryIntent.putExtra(RT2Const.IPC.MSG_PUMP_history_key, storeForHistoryViewer);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(sendHistoryIntent);
break;
case RT2Const.IPC.MSG_ServiceResult:
Log.i(TAG, "Received ServiceResult");
Bundle bundle = receivedIntent.getBundleExtra(RT2Const.IPC.bundleKey);
transport = new ServiceTransport(bundle);
if (transport.commandDidCompleteOK()) {
String originalCommandName = transport.getOriginalCommandName();
switch (originalCommandName) {
case "ReadPumpModel":
PumpModelResult modelResult = new PumpModelResult();
modelResult.initFromServiceResult(transport.getServiceResult());
String pumpModelString = modelResult.getPumpModel();
// GGW Tue Jul 12 02:29:54 UTC 2016: ok, now what do we do with the pump model?
showIdle();
break;
case "ReadPumpClock":
ReadPumpClockResult clockResult = new ReadPumpClockResult();
clockResult.initFromServiceResult(transport.getServiceResult());
TextView pumpTimeTextView = (TextView) findViewById(R.id.textViewPumpClockTime);
pumpTimeTextView.setText(clockResult.getTimeString());
showIdle();
break;
case "FetchPumpHistory":
storeForHistoryViewer = receivedIntent.getExtras().getBundle(RT2Const.IPC.bundleKey);
startActivity(new Intent(context, HistoryPageListActivity.class));
// wait for history viewer to announce "ready"
showIdle();
break;
case "RetrieveHistoryPage":
storeForHistoryViewer = receivedIntent.getExtras().getBundle(RT2Const.IPC.bundleKey);
startActivity(new Intent(context, HistoryPageListActivity.class));
// wait for history viewer to announce "ready"
showIdle();
break;
case "ISFProfile":
ISFProfile isfProfile = new ISFProfile();
isfProfile.initFromServiceResult(transport.getServiceResult());
// TODO: do something with isfProfile
showIdle();
break;
case "BasalProfile":
BasalProfile basalProfile = new BasalProfile();
basalProfile.initFromServiceResult(transport.getServiceResult());
// TODO: do something with basal profile
showIdle();
break;
case "BolusWizardCarbProfile":
BolusWizardCarbProfile carbProfile = new BolusWizardCarbProfile();
carbProfile.initFromServiceResult(transport.getServiceResult());
// TODO: do something with carb profile
showIdle();
break;
case "UpdatePumpStatus":
// rebroadcast for HAPP
break;
default:
Log.e(TAG, "Dunno what to do with this command completion: " + transport.getOriginalCommandName());
}
} else {
Log.e(TAG,"Command failed? " + transport.getOriginalCommandName());
}
break;
case RT2Const.IPC.MSG_ServiceNotification:
transport = new ServiceTransport(receivedIntent.getBundleExtra(RT2Const.IPC.bundleKey));
ServiceNotification notification = transport.getServiceNotification();
String note = notification.getNotificationType();
switch (note) {
case RT2Const.IPC.MSG_BLE_RileyLinkReady:
setRileylinkStatusMessage("OK");
break;
case RT2Const.IPC.MSG_PUMP_pumpFound:
setPumpStatusMessage("OK");
break;
case RT2Const.IPC.MSG_PUMP_pumpLost:
setPumpStatusMessage("Lost");
break;
case RT2Const.IPC.MSG_note_WakingPump:
showBusy("Waking Pump", 99);
break;
case RT2Const.IPC.MSG_note_FindingRileyLink:
showBusy("Finding RileyLink", 99);
break;
case RT2Const.IPC.MSG_note_Idle:
showIdle();
break;
default:
Log.e(TAG, "Unrecognized Notification: '" + note + "'");
}
break;
default:
Log.e(TAG, "Unrecognized intent action: " + receivedIntent.getAction());
}
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(RT2Const.local.INTENT_serviceConnected);
intentFilter.addAction(RT2Const.IPC.MSG_ServiceResult);
intentFilter.addAction(RT2Const.IPC.MSG_ServiceNotification);
intentFilter.addAction(RT2Const.local.INTENT_historyPageViewerReady);
linearProgressBar = (ProgressBar)findViewById(R.id.progressBarCommandActivity);
spinnyProgressBar = (ProgressBar)findViewById(R.id.progressBarSpinny);
LocalBroadcastManager.getInstance(MainApp.instance()).registerReceiver(mBroadcastReceiver, intentFilter);
}
/**
*
* GUI element functions
*
*/
private int mProgress = 0;
private int mSpinnyProgress = 0;
private ProgressBar linearProgressBar;
private ProgressBar spinnyProgressBar;
private static final int spinnyFPS = 10;
private Thread spinnyThread;
void showBusy(String activityString, int progress) {
mProgress = progress;
TextView tv = (TextView)findViewById(R.id.textViewActivity);
tv.setText(activityString);
linearProgressBar.setProgress(progress);
if (progress > 0) {
spinnyProgressBar.setVisibility(View.VISIBLE);
if (spinnyThread == null) {
spinnyThread = new Thread(new Runnable() {
@Override
public void run() {
while ((mProgress > 0) && (mProgress < 100)) {
mSpinnyProgress += 100 / spinnyFPS;
spinnyProgressBar.setProgress(mSpinnyProgress);
SystemClock.sleep(1000 / spinnyFPS);
}
spinnyThread = null;
}
});
spinnyThread.start();
}
} else {
spinnyProgressBar.setVisibility(View.INVISIBLE);
}
}
void showIdle() {
showBusy("Idle",0);
}
void setRileylinkStatusMessage(String statusMessage) {
TextView field = (TextView)findViewById(R.id.textViewFieldRileyLink);
field.setText(statusMessage);
}
void setPumpStatusMessage(String statusMessage) {
TextView field = (TextView)findViewById(R.id.textViewFieldPump);
field.setText(statusMessage);
}
public void onTunePumpButtonClicked(View view) {
MainApp.getServiceClientConnection().doTunePump();
}
public void onFetchHistoryButtonClicked(View view) {
/* does not work. Crashes sig 11 */
showBusy("Fetch history page 0",50);
MainApp.getServiceClientConnection().doFetchPumpHistory();
}
public void onFetchSavedHistoryButtonClicked(View view) {
showBusy("Fetching history (not saved)",50);
MainApp.getServiceClientConnection().doFetchSavedHistory();
}
public void onReadPumpClockButtonClicked(View view) {
showBusy("Reading Pump Clock",50);
MainApp.getServiceClientConnection().readPumpClock();
}
public void onGetISFProfileButtonClicked(View view) {
//ServiceCommand getISFProfileCommand = ServiceClientActions.makeReadISFProfileCommand();
//roundtripServiceClientConnection.sendServiceCommand(getISFProfileCommand);
MainApp.getServiceClientConnection().readISFProfile();
}
public void onViewEventLogButtonClicked(View view) {
startActivity(new Intent(getApplicationContext(),ServiceMessageViewListActivity.class));
}
public void onUpdateAllStatusButtonClicked(View view) {
MainApp.getServiceClientConnection().updateAllStatus();
}
public void onGetCarbProfileButtonClicked(View view) {
showBusy("Getting Carb Profile",1);
roundtripServiceClientConnection.sendServiceCommand(ServiceClientActions.makeReadBolusWizardCarbProfileCommand());
}
/* UI Setup */
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)){
mDrawerLayout.closeDrawers();
} else {
mDrawerLayout.openDrawer(GravityCompat.START);
}
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case android.R.id.home:
mDrawerLayout.openDrawer(mDrawerLinear);
return true;
default:
return true;
}
}
public void setupMenuAndToolbar() {
//Setup menu
mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
mDrawerLinear = (LinearLayout) findViewById(R.id.left_drawer);
toolbar = (Toolbar) findViewById(R.id.mainActivityToolbar);
Drawable logsIcon = getDrawable(R.drawable.file_chart);
Drawable historyIcon = getDrawable(R.drawable.history);
Drawable settingsIcon = getDrawable(R.drawable.settings);
Drawable catIcon = getDrawable(R.drawable.cat);
Drawable apsIcon = getDrawable(R.drawable.refresh);
logsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP);
historyIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP);
settingsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP);
catIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP);
apsIcon.setColorFilter(getResources().getColor(R.color.primary_dark), PorterDuff.Mode.SRC_ATOP);
ListView mDrawerList = (ListView)findViewById(R.id.navList);
ArrayList<NavItem> menuItems = new ArrayList<>();
menuItems.add(new NavItem("APS Integration", apsIcon));
menuItems.add(new NavItem("Pump History", historyIcon));
menuItems.add(new NavItem("Treatment Logs", logsIcon));
menuItems.add(new NavItem("Settings", settingsIcon));
menuItems.add(new NavItem("View LogCat", catIcon));
DrawerListAdapter adapterMenu = new DrawerListAdapter(this, menuItems);
mDrawerList.setAdapter(adapterMenu);
mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
//Check APS App Connectivity
sendAPSAppMessage(view);
break;
case 1:
//Pump History
startActivity(new Intent(getApplicationContext(), HistoryPageListActivity.class));
break;
case 2:
//Treatment Logs
startActivity(new Intent(getApplicationContext(), TreatmentHistory.class));
break;
case 3:
//Settings
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
break;
case 4:
//View LogCat
tools.showLogging();
break;
}
mDrawerLayout.closeDrawers();
}
});
ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,R.string.drawer_open, R.string.drawer_close) {
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
//Insulin Integration App, try and connect
//checkInsulinAppIntegration(false);
}
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
mDrawerToggle.syncState();
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerLayout.addDrawerListener(mDrawerToggle);
}
/* Functions for APS App Service */
//Our Service that APS App will connect to
private Messenger myService = null;
private ServiceConnection myConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
myService = new Messenger(service);
//Broadcast there has been a connection
Intent intent = new Intent("APS_CONNECTED");
LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(intent);
}
public void onServiceDisconnected(ComponentName className) {
myService = null;
//FYI, only called if Service crashed or was killed, not on unbind
}
};
public void sendAPSAppMessage(final View view)
{
//listen out for a successful connection
apsAppConnected = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Resources appR = view.getContext().getResources();
CharSequence txt = appR.getText(appR.getIdentifier("app_name", "string", view.getContext().getPackageName()));
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString(RT2Const.commService.ACTION,RT2Const.commService.OUTGOING_TEST_MSG);
bundle.putString(RT2Const.commService.REMOTE_APP_NAME, txt.toString());
msg.setData(bundle);
try {
myService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
//cannot Bind to service
Snackbar snackbar = Snackbar
.make(view, "error sending msg: " + e.getMessage(), Snackbar.LENGTH_INDEFINITE);
snackbar.show();
}
if (apsAppConnected != null) LocalBroadcastManager.getInstance(MainApp.instance()).unregisterReceiver(apsAppConnected); //Stop listening for new connections
MainApp.instance().unbindService(myConnection);
}
};
LocalBroadcastManager.getInstance(MainApp.instance()).registerReceiver(apsAppConnected, new IntentFilter("APS_CONNECTED"));
connect_to_aps_app(MainApp.instance());
}
//Connect to the APS App Treatments Service
private void connect_to_aps_app(Context c){
// TODO: 16/06/2016 add user selected aps app
Intent intent = new Intent("com.hypodiabetic.happ.services.TreatmentService");
intent.setPackage("com.hypodiabetic.happ");
c.bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
}
}
class NavItem {
String mTitle;
Drawable mIcon;
public NavItem(String title, Drawable icon) {
mTitle = title;
mIcon = icon;
}
}
class DrawerListAdapter extends BaseAdapter {
Context mContext;
ArrayList<NavItem> mNavItems;
public DrawerListAdapter(Context context, ArrayList<NavItem> navItems) {
mContext = context;
mNavItems = navItems;
}
@Override
public int getCount() {
return mNavItems.size();
}
@Override
public Object getItem(int position) {
return mNavItems.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.menu_item, null);
}
else {
view = convertView;
}
TextView titleView = (TextView) view.findViewById(R.id.menuText);
ImageView iconView = (ImageView) view.findViewById(R.id.menuIcon);
titleView.setText( mNavItems.get(position).mTitle);
iconView.setBackground(mNavItems.get(position).mIcon);
return view;
}
}

View file

@ -0,0 +1,48 @@
package com.gxwtech.roundtrip2;
import android.app.Application;
import io.realm.Realm;
import io.realm.RealmConfiguration;
/**
* Created by Tim on 15/06/2016.
*/
public class MainApp extends Application {
private static MainApp sInstance;
private static ServiceClientConnection serviceClientConnection;
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
serviceClientConnection = new ServiceClientConnection();
//initialize Realm
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(instance())
.name("rt2.realm")
.schemaVersion(0)
.deleteRealmIfMigrationNeeded() // TODO: 03/08/2016 @TIM remove
.build();
Realm.setDefaultConfiguration(realmConfiguration);
}
public static MainApp instance() {
return sInstance;
}
public static ServiceClientConnection getServiceClientConnection(){
if (serviceClientConnection == null) {
serviceClientConnection = new ServiceClientConnection();
}
return serviceClientConnection;
}
// TODO: 09/07/2016 @TIM uncomment ServiceClientConnection once class is added
}

View file

@ -0,0 +1,135 @@
package com.gxwtech.roundtrip2;
/**
* Created by geoff on 6/5/16.
*/
public class RT2Const {
public static final String RT2Prefix = "com.gxwtech.roundtrip2.";
public class IPC {
public static final String Prefix = RT2Prefix + "IPC.";
// This message is used to bind the "replyTo" field
public static final int MSG_clientRegistered = 63; // arbitrary
public static final int MSG_unregisterClient = 64;
public static final int MSG_registerClient = 65;
// used in IPC to mark a message as a generic IPC message
public static final int MSG_IPC = 66;
// used as the key to find the message in the bundle.
public static final String messageKey = Prefix + "messageKey";
// used as a key for the bundle, when the bundle is packed into an Intent
public static final String bundleKey = Prefix + "bundleKey";
// a field to hold the instant (milliseconds since 1970) the message
// was sent or received by the CLIENT
public static final String instantKey = Prefix + "instantKey";
// key for the "command" string in a bundle
public static final String commandKey = Prefix + "commandKey";
// key for the command-response bundle in a result bundle
public static final String serviceResultKey = Prefix + "commandResponse";
// used by gui to pass the address of the Rileylink to the Service
// has an 'address' field with the string address.
public static final String MSG_BLE_useThisDevice = Prefix + "MSG_BLE_useThisDevice";
public static final String MSG_BLE_useThisDevice_addressKey = Prefix + "MSG_BLE_useThisDevice_addressKey";
// used by gui to tell service that BLE access is denied by user.
public static final String MSG_BLE_accessDenied = Prefix + "MSG_BLE_accessDenied";
public static final String MSG_BLE_accessGranted = Prefix + "MSG_BLE_accessGranted";
// used by service to ask user for Bluetooth permission
public static final String MSG_BLE_requestAccess = Prefix + "MSG_BLE_requestAccess";
public static final String MSG_BLE_RileyLinkReady = Prefix + "MSG_BLE_RileyLinkReady";
// used to pass the pump ID from GUI to service
// has a 'pumpID' field containing a six digit String
public static final String MSG_PUMP_useThisAddress = Prefix + "MSG_PUMP_useThisAddress";
public static final String MSG_PUMP_useThisAddress_pumpIDKey = Prefix + "MSG_PUMP_useThisAddress_pumpIDKey";
// These are used to pass information about the pump from the service to the GUI.
// has a 'model' field
//public static final String MSG_PUMP_reportedPumpModel = Prefix + "MSG_PUMP_reportedPumpModel";
public static final String MSG_PUMP_pumpFound = Prefix + "MSG_PUMP_pumpFound";
public static final String MSG_PUMP_pumpLost = Prefix + "MSG_PUMP_pumpLost";
public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump";
public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune";
public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory";
public static final String MSG_PUMP_history = Prefix + "MSG_PUMP_history";
public static final String MSG_PUMP_history_key = Prefix + "MSG_PUMP_history_key";
public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory";
// interface for ServiceCommand/ServiceResult
public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand";
public static final String MSG_ServiceResult = Prefix + "MSG_ServiceResult";
public static final String MSG_ServiceNotification = Prefix + "MSG_ServiceNotification";
// These are notifications to GUIs to let them know what's happening
public static final String MSG_note_Idle = Prefix + "MSG_note_Idle";
public static final String MSG_note_FindingRileyLink = Prefix + "MSG_note_FindingRileyLink";
public static final String MSG_note_WakingPump = Prefix + "MSG_note_WakingPump";
public static final String MSG_note_TaskProgress = Prefix + "MSG_note_TaskProgress";
}
public class local {
// These are local to the GUI activities
public static final String Prefix = RT2Prefix + "local.";
public static final String INTENT_serviceConnected = Prefix + "INTENT_serviceConnected";
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 INTENT_historyPageViewerReady = Prefix + "I'm ready, hit me up";
public static final String INTENT_historyPageBundleIncoming = Prefix + "Here's the kitchen sink";
}
public class serviceLocal {
public static final String Prefix = RT2Prefix + "serviceLocal.";
// for local broadcasts annoucing connectivity events
public static final String INTENT_seekRileylink = Prefix + "INTENT_seekRileylink";
public static final String bluetooth_connected = Prefix + "bluetooth_connected";
public static final String bluetooth_disconnected = Prefix + "bluetooth_disconnected";
public static final String BLE_services_discovered = Prefix + "BLE_services_discovered";
public static final String ipcBound = Prefix + "ipcBound";
// primary shared preferences file identifier
public static final String sharedPreferencesKey = Prefix + "sharedPreferencesKey";
// These are used to identify shared preference items
public static final String pumpIDKey = Prefix + "PumpIDKey";
public static final String rileylinkAddressKey = Prefix + "rileylinkAddressKey";
public static final String prefsLastGoodPumpFrequency = Prefix + "prefsLastGoodPumpFrequency";
// The the key to identify the hashCode() of the msg.replyTo, when the bundle is moved to an intent.
public static final String IPCReplyTo_hashCodeKey = Prefix + "IPCReplyTo_hashCodeKey";
// This is sent from the PumpManager to RoundtripService at the completion of a pump command session.
public static final String INTENT_sessionCompleted = Prefix + "INTENT_sessionCompleted";
}
public class commService {
//Data
public static final String ACTION = "ACTION";
public static final String DATE_REQUESTED = "DATE_REQUESTED";
public static final String INTEGRATION_OBJECTS = "INTEGRATION_OBJECTS";
public static final String TREATMENT_OBJECTS = "TREATMENT_OBJECTS";
public static final String PUMP = "PUMP";
public static final String REMOTE_APP_NAME = "REMOTE_APP_NAME";
//Incoming actions
public static final String INCOMING_NEW_TREATMENTS = "NEW_TREATMENTS";
public static final String INCOMING_TEST_MSG = "TEST_MSG";
//Outgoing actions
public static final String OUTGOING_TREATMENT_UPDATES = "TREATMENT_UPDATES";
public static final String OUTGOING_TEST_MSG = "TEST_MSG";
}
public class safety {
public static final int INCOMING_REQUEST_MAX_AGE = 10; //mins
public static final int TREATMENT_MAX_AGE = 8; //mins
}
}

View file

@ -0,0 +1,343 @@
package com.gxwtech.roundtrip2;
import android.Manifest;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Toast;
import android.util.Log;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import com.gxwtech.roundtrip2.util.LocationHelper;
import java.util.ArrayList;
import java.util.List;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class RileyLinkScan extends AppCompatActivity {
private final static String TAG = "RileyLinkScan";
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mLEScanner;
private LeDeviceListAdapter mLeDeviceListAdapter;
public boolean mScanning;
private Handler mHandler;
public Snackbar snackbar;
public ScanSettings settings;
public List<ScanFilter> filters;
public ListView listBTScan;
public Toolbar toolbarBTScan;
public Context mContext = this;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary.
private static final int REQUEST_ENABLE_BT = 30242; // arbitrary
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_riley_link_scan);
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
mHandler = new Handler();
mLeDeviceListAdapter = new LeDeviceListAdapter();
listBTScan = (ListView) findViewById(R.id.listBTScan);
listBTScan.setAdapter(mLeDeviceListAdapter);
listBTScan.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textview = (TextView) view.findViewById(R.id.device_address);
String bleAddress = textview.getText().toString();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
prefs.edit().putString(RT2Const.serviceLocal.rileylinkAddressKey, bleAddress).apply();
//Notify that we have a new rileylinkAddressKey
LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(new Intent(RT2Const.local.INTENT_NEW_rileylinkAddressKey));
Log.d(TAG, "New rileylinkAddressKey: " + bleAddress);
//Notify that we have a new pumpIDKey
LocalBroadcastManager.getInstance(MainApp.instance()).sendBroadcast(new Intent(RT2Const.local.INTENT_NEW_pumpIDKey));
finish();
}
});
toolbarBTScan = (Toolbar) findViewById(R.id.toolbarBTScan);
toolbarBTScan.setTitle(R.string.title_activity_riley_link_scan);
setSupportActionBar(toolbarBTScan);
snackbar = Snackbar.make(findViewById(R.id.RileyLinkScan), "Scanning...",Snackbar.LENGTH_INDEFINITE);
snackbar.setAction("STOP", new View.OnClickListener() {
@Override
public void onClick(View view) {
scanLeDevice(false);
}
});
startScanBLE();
}
@Override
protected void onPause() {
super.onPause();
scanLeDevice(false);
mLeDeviceListAdapter.clear();
mLeDeviceListAdapter.notifyDataSetChanged();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_bluetooth_scan, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.miScan:
startScanBLE();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void startScanBLE(){
// 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.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.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.
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 = new ArrayList<ScanFilter>();
scanLeDevice(true);
}
}
}
@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 void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mLEScanner.stopScan(mScanCallback);
Log.d(TAG, "scanLeDevice: Scanning Stop");
//Toast.makeText(mContext, "Scanning finished", Toast.LENGTH_SHORT).show();
snackbar.dismiss();
}
}, SCAN_PERIOD);
mScanning = true;
mLEScanner.startScan(mScanCallback);
Log.d(TAG, "scanLeDevice: Scanning Start");
//Toast.makeText(this, "Scanning", Toast.LENGTH_SHORT).show();
snackbar.show();
} else {
mScanning = false;
mLEScanner.stopScan(mScanCallback);
Log.d(TAG, "scanLeDevice: Scanning Stop");
//Toast.makeText(this, "Scanning finished", Toast.LENGTH_SHORT).show();
snackbar.dismiss();
}
}
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
final BluetoothDevice device = result.getDevice();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (device.getName() != null && device.getName().length() > 0) {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
Log.d(TAG, "Found BLE" + device.getName());
}
}
});
}
@Override
public void onBatchScanResults(final List<ScanResult> results) {
runOnUiThread(new Runnable() {
@Override
public void run() {
for (ScanResult result: results) {
BluetoothDevice device = result.getDevice();
if (device.getName() != null && device.getName().length() > 0) {
mLeDeviceListAdapter.addDevice(device);
Log.d(TAG, "Found BLE" + result.toString());
} else {
Log.e(TAG, "Found BLE, but name appears to be missing. Ignoring. " + device.getAddress());
}
}
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
@Override
public void onScanFailed(int errorCode) {
Log.e("Scan Failed", "Error Code: " + errorCode);
Toast.makeText(mContext, "Scan Failed " + errorCode, Toast.LENGTH_LONG).show();
}
};
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList<BluetoothDevice> mLeDevices;
private LayoutInflater mInflator;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList<>();
mInflator = RileyLinkScan.this.getLayoutInflater();
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
notifyDataSetChanged();
}
}
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.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.listitem_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
String deviceName = device.getName();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
if(prefs.getString(RT2Const.serviceLocal.rileylinkAddressKey, "").compareTo(device.getAddress()) == 0) {
viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light));
viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light));
deviceName += " (" + getResources().getString(R.string.selected_device) + ")";
}
viewHolder.deviceName.setText(deviceName);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
}

View file

@ -0,0 +1,21 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLink;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Created by geoff on 5/30/16.
*/
public class FrequencyScanResults {
public ArrayList<FrequencyTrial> trials = new ArrayList<>();
public double bestFrequencyMHz = 0.0;
public void sort() {
Collections.sort(trials, new Comparator<FrequencyTrial>() {
@Override
public int compare(FrequencyTrial trial1, FrequencyTrial trial2) {
return trial1.averageRSSI.compareTo(trial2.averageRSSI);
}
});
}
}

View file

@ -0,0 +1,11 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLink;
/**
* Created by geoff on 5/30/16.
*/
public class FrequencyTrial {
public int tries = 0;
public int successes = 0;
public Double averageRSSI = 0.0;
public double frequencyMHz = 0.0;
}

View file

@ -0,0 +1,598 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLink;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpy;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpyResponse;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RadioPacket;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RadioResponse;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.ButtonPressCarelinkMessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.CarelinkShortMessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.GetHistoryPageCarelinkMessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.GetPumpModelCarelinkMessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.MessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.MessageType;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages.PumpAckMessageBody;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PacketType;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.Record;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpMessage;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.ServiceData.PumpStatusResult;
import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.StringUtil;
import org.joda.time.Duration;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.Instant;
import org.joda.time.LocalDateTime;
import java.util.ArrayList;
/**
* Created by geoff on 5/30/16.
*/
public class PumpManager {
private static final String TAG = "PumpManager";
public double[] scanFrequencies = {916.45, 916.50, 916.55, 916.60, 916.65, 916.70, 916.75, 916.80};
public static final int startSession_signal = 6656; // arbitrary.
//private long pumpAwakeUntil = 0;
private int pumpAwakeForMinutes = 6;
private final RFSpy rfspy;
private byte[] pumpID;
public boolean DEBUG_PUMPMANAGER = true;
private final Context context;
private SharedPreferences prefs;
private Instant lastGoodPumpCommunicationTime = new Instant(0);
public PumpManager(Context context, RFSpy rfspy, byte[] pumpID) {
this.context = context;
this.rfspy = rfspy;
this.pumpID = pumpID;
prefs = context.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE);
}
private PumpMessage runCommandWithArgs(PumpMessage msg) {
PumpMessage rval;
PumpMessage shortMessage = makePumpMessage(msg.messageType,new CarelinkShortMessageBody(new byte[]{0}));
// look for ack from short message
PumpMessage shortResponse = sendAndListen(shortMessage);
if (shortResponse.messageType.mtype == MessageType.PumpAck) {
rval = sendAndListen(msg);
return rval;
} else {
Log.e(TAG,"runCommandWithArgs: Pump did not ack Attention packet");
}
return new PumpMessage();
}
protected PumpMessage sendAndListen(PumpMessage msg) {
return sendAndListen(msg,2000);
}
// All pump communications go through this function.
protected PumpMessage sendAndListen(PumpMessage msg, int timeout_ms) {
boolean showPumpMessages = true;
if (showPumpMessages) {
Log.i(TAG,"Sent:"+ByteUtil.shortHexString(msg.getTxData()));
}
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()),timeout_ms);
PumpMessage rval = new PumpMessage(resp.getRadioResponse().getPayload());
if (rval.isValid()) {
// Mark this as the last time we heard from the pump.
rememberLastGoodPumpCommunicationTime();
}
if (showPumpMessages) {
Log.i(TAG,"Received:"+ByteUtil.shortHexString(resp.getRadioResponse().getPayload()));
}
return rval;
}
public Page getPumpHistoryPage(int pageNumber) {
RawHistoryPage rval = new RawHistoryPage();
wakeup(pumpAwakeForMinutes);
PumpMessage getHistoryMsg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_HISTORY), new GetHistoryPageCarelinkMessageBody(pageNumber));
//Log.i(TAG,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData()));
// Ask the pump to transfer history (we get first frame?)
PumpMessage firstResponse = runCommandWithArgs(getHistoryMsg);
//Log.i(TAG,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents()));
PumpMessage ackMsg = makePumpMessage(MessageType.PumpAck,new PumpAckMessageBody());
GetHistoryPageCarelinkMessageBody currentResponse = new GetHistoryPageCarelinkMessageBody(firstResponse.getMessageBody().getTxData());
int expectedFrameNum = 1;
boolean done = false;
//while (expectedFrameNum == currentResponse.getFrameNumber()) {
int failures = 0;
while (!done) {
// examine current response for problems.
byte[] frameData = currentResponse.getFrameData();
if ((frameData != null) && (frameData.length > 0) && currentResponse.getFrameNumber() == expectedFrameNum) {
// success! got a frame.
if (frameData.length != 64) {
Log.w(TAG,"Expected frame of length 64, got frame of length " + frameData.length);
// but append it anyway?
}
// handle successful frame data
rval.appendData(currentResponse.getFrameData());
RoundtripService.getInstance().announceProgress(((100/16) * currentResponse.getFrameNumber()+1));
Log.i(TAG,"getPumpHistoryPage: Got frame "+currentResponse.getFrameNumber());
// Do we need to ask for the next frame?
if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722
expectedFrameNum++;
} else {
done = true; // successful completion
}
} else {
if (frameData == null) {
Log.e(TAG,"null frame data, retrying");
} else if (currentResponse.getFrameNumber() != expectedFrameNum) {
Log.w(TAG, String.format("Expected frame number %d, received %d (retrying)", expectedFrameNum, currentResponse.getFrameNumber()));
} else if (frameData.length == 0) {
Log.w(TAG, "Frame has zero length, retrying");
}
failures++;
if (failures == 6) {
Log.e(TAG,String.format("6 failures in attempting to download frame %d of page %d, giving up.",expectedFrameNum,pageNumber));
done = true; // failure completion.
}
}
if (!done) {
// ask for next frame
PumpMessage nextMsg = sendAndListen(ackMsg);
currentResponse = new GetHistoryPageCarelinkMessageBody(nextMsg.getMessageBody().getTxData());
}
}
if (rval.getLength() != 1024) {
Log.w(TAG,"getPumpHistoryPage: short page. Expected length of 1024, found length of "+rval.getLength());
}
if (rval.isChecksumOK() == false) {
Log.e(TAG,"getPumpHistoryPage: checksum is wrong");
}
rval.dumpToDebug();
Page page = new Page();
//page.parseFrom(rval.getData(),PumpModel.MM522);
page.parseFrom(rval.getData(), PumpModel.MM522);
return page;
}
public ArrayList<Page> getAllHistoryPages() {
ArrayList<Page> pages = new ArrayList<>();
for (int pageNum = 0; pageNum < 16; pageNum++) {
pages.add(getPumpHistoryPage(pageNum));
}
return pages;
}
public ArrayList<Page> getHistoryEventsSinceDate(Instant when) {
ArrayList<Page> pages = new ArrayList<>();
for (int pageNum = 0; pageNum < 16; pageNum++) {
pages.add(getPumpHistoryPage(pageNum));
for (Page page : pages) {
for (Record r : page.mRecordList) {
LocalDateTime timestamp = r.getTimestamp().getLocalDateTime();
Log.i(TAG, "Found record: (" + r.getClass().getSimpleName() + ") " + timestamp.toString());
}
}
}
return pages;
}
private LocalDateTime parsePumpRTCBytes(byte[] bytes) {
if (bytes == null) return null;
if (bytes.length < 7) return null;
int hours = ByteUtil.asUINT8(bytes[0]);
int minutes = ByteUtil.asUINT8(bytes[1]);
int seconds = ByteUtil.asUINT8(bytes[2]);
int year = (ByteUtil.asUINT8(bytes[4]) & 0x3f) + 1984;
int month = ByteUtil.asUINT8(bytes[5]);
int day = ByteUtil.asUINT8(bytes[6]);
try {
LocalDateTime pumpTime = new LocalDateTime(year, month, day, hours, minutes, seconds);
return pumpTime;
} catch (IllegalFieldValueException e) {
Log.e(TAG,String.format("parsePumpRTCBytes: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d",year,month,day,hours,minutes,seconds));
return null;
}
}
public ReadPumpClockResult getPumpRTC() {
ReadPumpClockResult rval = new ReadPumpClockResult();
wakeup(pumpAwakeForMinutes);
PumpMessage getRTCMsg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_RTC), new CarelinkShortMessageBody(new byte[]{0}));
Log.i(TAG,"getPumpRTC: " + ByteUtil.shortHexString(getRTCMsg.getTxData()));
PumpMessage response = sendAndListen(getRTCMsg);
if (response.isValid()) {
byte[] receivedData = response.getContents();
if (receivedData != null) {
if (receivedData.length >= 9) {
LocalDateTime pumpTime = parsePumpRTCBytes(ByteUtil.substring(receivedData, 2, 7));
if (pumpTime != null) {
rval.setTime(pumpTime);
rval.setResultOK();
} else {
rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE);
}
} else {
rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE);
}
} else {
rval.setResultError(ServiceResult.ERROR_MALFORMED_PUMP_RESPONSE);
}
} else {
rval.setResultError(ServiceResult.ERROR_INVALID_PUMP_RESPONSE);
}
return rval;
}
public PumpModel getPumpModel() {
wakeup(pumpAwakeForMinutes);
PumpMessage msg = makePumpMessage(new MessageType(MessageType.GetPumpModel), new GetPumpModelCarelinkMessageBody());
Log.i(TAG,"getPumpModel: " + ByteUtil.shortHexString(msg.getTxData()));
PumpMessage response = sendAndListen(msg);
Log.i(TAG,"getPumpModel response: " + ByteUtil.shortHexString(response.getContents()));
byte[] contents = response.getContents();
PumpModel rval = PumpModel.UNSET;
if (contents != null) {
if (contents.length >= 7) {
rval = PumpModel.fromString(StringUtil.fromBytes(ByteUtil.substring(contents,3,3)));
} else {
Log.w(TAG,"getPumpModel: Cannot return pump model number: data is too short.");
}
} else {
Log.w(TAG,"getPumpModel: Cannot return pump model number: null response");
}
return rval;
}
public ISFTable getPumpISFProfile() {
wakeup(pumpAwakeForMinutes);
PumpMessage getISFProfileMessage = makePumpMessage(new MessageType(MessageType.GetISFProfile),new CarelinkShortMessageBody());
PumpMessage resp = sendAndListen(getISFProfileMessage);
ISFTable table = new ISFTable();
table.parseFrom(resp.getContents());
return table;
}
public PumpMessage getBolusWizardCarbProfile() {
wakeup(pumpAwakeForMinutes);
PumpMessage getCarbProfileMessage = makePumpMessage(new MessageType(MessageType.CMD_M_READ_CARB_RATIOS),new CarelinkShortMessageBody());
PumpMessage resp = sendAndListen(getCarbProfileMessage);
return resp;
}
public void tryoutPacket(byte[] pkt) {
sendAndListen(makePumpMessage(pkt));
}
public void hunt() {
tryoutPacket(new byte[] {MessageType.CMD_M_READ_PUMP_STATUS,0});
tryoutPacket(new byte[] {MessageType.CMD_M_READ_FIRMWARE_VER,0});
tryoutPacket(new byte[] {MessageType.CMD_M_READ_INSULIN_REMAINING,0});
}
// See ButtonPressCarelinkMessageBody
public void pressButton(int which) {
wakeup(pumpAwakeForMinutes);
PumpMessage pressButtonMessage = makePumpMessage(new MessageType(MessageType.CMD_M_KEYPAD_PUSH),new ButtonPressCarelinkMessageBody(which));
PumpMessage resp = sendAndListen(pressButtonMessage);
if (resp.messageType.mtype != MessageType.PumpAck) {
Log.e(TAG,"Pump did not ack button press.");
}
}
public void wakeup(int duration_minutes) {
// 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
pumpAwakeForMinutes = duration_minutes;
Instant lastGood = getLastGoodPumpCommunicationTime();
// Instant lastGoodPlus = lastGood.plus(new Duration(pumpAwakeForMinutes * 60 * 1000));
Instant lastGoodPlus = lastGood.plus(new Duration(1 * 60 * 1000));
Instant now = Instant.now();
if (now.compareTo(lastGoodPlus) > 0) {
Log.i(TAG,"Waking pump...");
PumpMessage msg = makePumpMessage(new MessageType(MessageType.PowerOn), new CarelinkShortMessageBody(new byte[]{(byte) duration_minutes}));
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), (byte) 0, (byte) 200, (byte) 0, (byte) 0, 15000, (byte) 0);
Log.i(TAG, "wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
} else {
Log.v(TAG,"Last pump communication was recent, not waking pump.");
}
}
public void setRadioFrequencyForPump(double freqMHz) {
rfspy.setBaseFrequency(freqMHz);
}
public double tuneForPump() {
return scanForPump(scanFrequencies);
}
private int tune_tryFrequency(double freqMHz) {
rfspy.setBaseFrequency(freqMHz);
PumpMessage msg = makePumpMessage(new MessageType(MessageType.GetPumpModel),new GetPumpModelCarelinkMessageBody());
RadioPacket pkt = new RadioPacket(msg.getTxData());
RFSpyResponse resp = rfspy.transmitThenReceive(pkt,(byte)0,(byte)0,(byte)0,(byte)0,rfspy.EXPECTED_MAX_BLUETOOTH_LATENCY_MS,(byte)0);
if (resp.wasTimeout()) {
Log.w(TAG,String.format("tune_tryFrequency: no pump response at frequency %.2f",freqMHz));
} else if (resp.looksLikeRadioPacket()) {
RadioResponse radioResponse = new RadioResponse(resp.getRaw());
if (radioResponse.isValid()) {
Log.w(TAG,String.format("tune_tryFrequency: saw response level %d at frequency %.2f",radioResponse.rssi,freqMHz));
return radioResponse.rssi;
} else {
Log.w(TAG,"tune_tryFrequency: invalid radio response:"+ByteUtil.shortHexString(radioResponse.getPayload()));
}
}
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
Log.e(TAG,"quickTuneForPump: failed to find pump");
} else {
rfspy.setBaseFrequency(betterFrequency);
if (betterFrequency != startFrequencyMHz) {
Log.i(TAG, String.format("quickTuneForPump: new frequency is %.2fMHz", betterFrequency));
} else {
Log.i(TAG, String.format("quickTuneForPump: pump frequency is the same: %.2fMHz", startFrequencyMHz));
}
}
return betterFrequency;
}
private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) {
Log.i(TAG,"Doing quick radio tune for pump ID " + pumpID);
wakeup(pumpAwakeForMinutes);
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;
}
private double scanForPump(double[] frequencies) {
Log.i(TAG,"Scanning for pump ID " + pumpID);
wakeup(pumpAwakeForMinutes);
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++) {
PumpMessage msg = makePumpMessage(new MessageType(MessageType.GetPumpModel), new GetPumpModelCarelinkMessageBody());
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()),(byte) 0, (byte) 0, (byte) 0, (byte) 0, rfspy.EXPECTED_MAX_BLUETOOTH_LATENCY_MS, (byte) 0);
if (resp.wasTimeout()) {
Log.e(TAG, String.format("scanForPump: Failed to find pump at frequency %.2f", frequencies[i]));
} else if (resp.looksLikeRadioPacket()) {
RadioResponse radioResponse = new RadioResponse(resp.getRaw());
if (radioResponse.isValid()) {
sumRSSI += radioResponse.rssi;
trial.successes++;
} else {
Log.w(TAG,"Failed to parse radio response: " + ByteUtil.shortHexString(resp.getRaw()));
}
} else {
Log.e(TAG, "scanForPump: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
}
trial.tries++;
}
sumRSSI += -99.0 * (trial.tries - trial.successes);
trial.averageRSSI = (double)(sumRSSI) / (double)(trial.tries);
results.trials.add(trial);
}
results.sort(); // sorts in ascending order
Log.d(TAG,"Sorted scan results:");
for (int k=0; k<results.trials.size(); k++) {
FrequencyTrial one = results.trials.get(k);
Log.d(TAG,String.format("Scan Result[%d]: Freq=%.2f, avg RSSI = %f",k,one.frequencyMHz, one.averageRSSI));
}
FrequencyTrial bestTrial = results.trials.get(results.trials.size()-1);
results.bestFrequencyMHz = bestTrial.frequencyMHz;
if (bestTrial.successes > 0) {
rfspy.setBaseFrequency(results.bestFrequencyMHz);
return results.bestFrequencyMHz;
} else {
Log.e(TAG,"No pump response during scan.");
return 0.0;
}
}
private PumpMessage makePumpMessage(MessageType messageType, MessageBody messageBody) {
PumpMessage msg = new PumpMessage();
msg.init(new PacketType(PacketType.Carelink),pumpID,messageType,messageBody);
return msg;
}
private PumpMessage makePumpMessage(byte msgType, MessageBody body) {
return makePumpMessage(new MessageType(msgType),body);
}
private PumpMessage makePumpMessage(byte[] typeAndBody) {
PumpMessage msg = new PumpMessage();
msg.init(ByteUtil.concat(ByteUtil.concat(new byte[]{(byte)0xa7},pumpID),typeAndBody));
return msg;
}
private void rememberLastGoodPumpCommunicationTime() {
lastGoodPumpCommunicationTime = Instant.now();
SharedPreferences.Editor ed = prefs.edit();
ed.putLong("lastGoodPumpCommunicationTime",lastGoodPumpCommunicationTime.getMillis());
ed.commit();
}
private Instant getLastGoodPumpCommunicationTime() {
// If we have a value of zero, we need to load from prefs.
if (lastGoodPumpCommunicationTime.getMillis() == new Instant(0).getMillis()) {
lastGoodPumpCommunicationTime = new Instant(prefs.getLong("lastGoodPumpCommunicationTime",0));
// Might still be zero, but that's fine.
}
double minutesAgo = (Instant.now().getMillis() - lastGoodPumpCommunicationTime.getMillis()) / (1000.0 * 60.0);
Log.v(TAG,"Last good pump communication was " + minutesAgo + " minutes ago.");
return lastGoodPumpCommunicationTime;
}
public PumpMessage getRemainingBattery() {
PumpMessage getBatteryMsg = makePumpMessage(new MessageType(MessageType.GetBattery),new CarelinkShortMessageBody());
PumpMessage resp = sendAndListen(getBatteryMsg);
return resp;
}
public PumpMessage getRemainingInsulin() {
PumpMessage msg = makePumpMessage(new MessageType(MessageType.CMD_M_READ_INSULIN_REMAINING),new CarelinkShortMessageBody());
PumpMessage resp = sendAndListen(msg);
return resp;
}
public PumpMessage getCurrentBasalRate() {
PumpMessage msg = makePumpMessage(new MessageType(MessageType.ReadTempBasal), new CarelinkShortMessageBody());
PumpMessage resp = sendAndListen(msg);
return resp;
}
PumpManagerStatus pumpManagerStatus = new PumpManagerStatus();
public PumpManagerStatus getPumpManagerStatus() { return pumpManagerStatus; }
public void updatePumpManagerStatus() {
PumpMessage resp = getRemainingBattery();
if (resp.isValid()) {
byte[] remainingBatteryBytes = resp.getContents();
if (remainingBatteryBytes != null) {
if (remainingBatteryBytes.length == 5) {
/**
* 0x72 0x03, 0x00, 0x00, 0x82
* meaning what ????
*/
pumpManagerStatus.remainBattery = ByteUtil.asUINT8(remainingBatteryBytes[5]);
}
}
}
resp = getRemainingInsulin();
byte[] insulinRemainingBytes = resp.getContents();
if (insulinRemainingBytes != null) {
if (insulinRemainingBytes.length == 4) {
/* 0x73 0x02 0x05 0xd2
* the 0xd2 (210) represents 21 units remaining.
*/
double insulinUnitsRemaining = ByteUtil.asUINT8(insulinRemainingBytes[3]) / 10.0;
pumpManagerStatus.remainUnits = insulinUnitsRemaining;
}
}
/* current basal */
resp = getCurrentBasalRate();
byte[] basalRateBytes = resp.getContents();
if (basalRateBytes != null) {
if (basalRateBytes.length == 2) {
/**
* 0x98 0x06
* 0x98 is "basal rate"
* 0x06 is what? Not currently running a temp basal, current basal is "standard" at 0
*/
double basalRate = ByteUtil.asUINT8(basalRateBytes[1]);
pumpManagerStatus.currentBasal = basalRate;
}
}
// get last bolus amount
// get last bolus time
// get tempBasalInProgress
// get tempBasalRatio
// get tempBasalRemainMin
// get tempBasalStart
// get pump time
ReadPumpClockResult clockResult = getPumpRTC();
if (clockResult.resultIsOK()) {
pumpManagerStatus.time = clockResult.getTime().toDate();
}
// get last sync time
}
public void testPageDecode() {
byte[] raw = new byte[] {(byte)0x6D, (byte)0x62, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x63, (byte)0x10, (byte)0x6D, (byte)0x63, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
(byte)0x01, (byte)0x01, (byte)0x00, (byte)0x5A, (byte)0xA5, (byte)0x49, (byte)0x04, (byte)0x10, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x00, (byte)0x6D, (byte)0xA5, (byte)0x49, (byte)0x04, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x10, (byte)0x6D, (byte)0x64, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x64, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x0C, (byte)0x00,
(byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x64, (byte)0x01, (byte)0x75, (byte)0x94, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x64, (byte)0x01, (byte)0x44, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x17, (byte)0x00, (byte)0x4E, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10, (byte)0x18, (byte)0x00, (byte)0x40, (byte)0x95, (byte)0x0D, (byte)0x05, (byte)0x10,
(byte)0x19, (byte)0x00, (byte)0x40, (byte)0x81, (byte)0x15, (byte)0x05, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x65, (byte)0x10, (byte)0x6D, (byte)0x65, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x1A, (byte)0x00, (byte)0x47, (byte)0x82, (byte)0x09, (byte)0x06,
(byte)0x10, (byte)0x1A, (byte)0x01, (byte)0x5C, (byte)0x82, (byte)0x09, (byte)0x06, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x66, (byte)0x10, (byte)0x6D, (byte)0x66, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x67, (byte)0x10, (byte)0x6D, (byte)0x67, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x68, (byte)0x10, (byte)0x6D, (byte)0x68, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x69, (byte)0x10, (byte)0x6D, (byte)0x69, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6A, (byte)0x10, (byte)0x6D, (byte)0x6A, (byte)0x10, (byte)0x05, (byte)0x0C,
(byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6B, (byte)0x10, (byte)0x6D, (byte)0x6B, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6C,
(byte)0x10, (byte)0x6D, (byte)0x6C, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6D, (byte)0x10, (byte)0x6D, (byte)0x6D, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6E, (byte)0x10, (byte)0x6D, (byte)0x6E, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x6F, (byte)0x10, (byte)0x6D, (byte)0x6F, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00,
(byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x19, (byte)0x00, (byte)0x40, (byte)0x81, (byte)0x03, (byte)0x10, (byte)0x10, (byte)0x1A, (byte)0x00, (byte)0x68, (byte)0x96, (byte)0x0A, (byte)0x10, (byte)0x10, (byte)0x1A, (byte)0x01, (byte)0x40, (byte)0x97, (byte)0x0A, (byte)0x10, (byte)0x10, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x70, (byte)0x10, (byte)0x6D, (byte)0x70, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x71, (byte)0x10, (byte)0x6D, (byte)0x71, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x72, (byte)0x10, (byte)0x6D, (byte)0x72, (byte)0x10, (byte)0x05, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x73, (byte)0x10, (byte)0x6D, (byte)0x73, (byte)0x10, (byte)0x05, (byte)0x0C,
(byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x0C, (byte)0x00, (byte)0xE8, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x07, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x74, (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x2C, (byte)0x79,
};
Page page = new Page();
page.parseFrom(raw,PumpModel.MM522);
page.parseByDates(raw,PumpModel.MM522);
page.parsePicky(raw,PumpModel.MM522);
Log.i(TAG,"testPageDecode: done");
}
}

View file

@ -0,0 +1,61 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLink;
import java.util.Date;
/**
* Created by geoff on 7/16/16.
*
* This class is intended to provide compatibility with HAPP, and was
* modeled after the PumpStatus class from happdanardriver
*
* happdanardriver/app/src/main/java/info/nightscout/danar/db/PumpStatus.java
*/
public class PumpManagerStatus {
public long getTimeIndex() {
return (long) Math.ceil(time.getTime() / 60000d );
}
public void setTimeIndex(long timeIndex) {
this.timeIndex = timeIndex;
}
public long timeIndex;
public Date time;
public double remainUnits = 0;
public int remainBattery = 0;
public double currentBasal = 0;
public int tempBasalInProgress = 0;
public int tempBasalRatio = 0;
public int tempBasalRemainMin = 0 ;
public Date last_bolus_time ;
public double last_bolus_amount = 0;
public Date tempBasalStart;
@Override
public String toString() {
return "PumpStatus{" +
"timeIndex=" + timeIndex +
", time=" + time +
", remainUnits=" + remainUnits +
", remainBattery=" + remainBattery +
", currentBasal=" + currentBasal +
", tempBasalInProgress=" + tempBasalInProgress +
", tempBasalRatio=" + tempBasalRatio +
", tempBasalRemainMin=" + tempBasalRemainMin +
", last_bolus_time=" + last_bolus_time +
", last_bolus_amount=" + last_bolus_amount +
", tempBasalStart=" + tempBasalStart +
'}';
}
}

View file

@ -0,0 +1,45 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLink;
import android.util.Log;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.CRC;
/**
* Created by geoff on 6/4/16.
*/
public class RawHistoryPage {
private static final String TAG = "RawHistoryPage";
byte[] data = new byte[0];
public RawHistoryPage() {
}
public void appendData(byte[] newdata) {
data = ByteUtil.concat(data,newdata);
}
public byte[] getData() {
return data;
}
public int getLength() {
return data.length;
}
public boolean isChecksumOK() {
if (getLength() != 1024) {
return false;
}
byte[] computedCRC = CRC.calculate16CCITT(ByteUtil.substring(data,0,1022));
return ((computedCRC[0] == data[1022]) && (computedCRC[1] == data[1023]));
}
public void dumpToDebug() {
int linesize = 80;
int offset = 0;
while (offset < data.length) {
int bytesToLog = linesize;
if (offset + linesize > data.length) {
bytesToLog = data.length - offset;
}
Log.d(TAG,ByteUtil.shortHexString(ByteUtil.substring(data,offset,bytesToLog)));
offset += linesize;
}
}
}

View file

@ -0,0 +1,20 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE;
import java.util.UUID;
/**
* Created by geoff on 5/26/16.
*/
public abstract class BLECommOperation {
public boolean timedOut = false;
public boolean interrupted = false;
// 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 null; }
}

View file

@ -0,0 +1,16 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations;
/**
* Created by geoff on 5/26/16.
*/
public class BLECommOperationResult {
public byte[] value;
public int resultCode;
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;
}

View file

@ -0,0 +1,62 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.GattAttributes;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Created by geoff on 5/26/16.
*/
public class CharacteristicReadOperation extends BLECommOperation {
private static final String TAG = "CharacteristicReadOp";
private BluetoothGatt gatt;
private BluetoothGattCharacteristic characteristic;
private byte[] value;
private Semaphore operationComplete = new Semaphore(0,true);
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.e(TAG,"Timeout waiting for gatt write operation to complete");
timedOut = true;
}
} catch (InterruptedException e) {
Log.e(TAG,"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.e(TAG,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();
}
@Override
public byte[] getValue() {
return value;
}
}

View file

@ -0,0 +1,66 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.GattAttributes;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Created by geoff on 5/26/16.
*/
public class CharacteristicWriteOperation extends BLECommOperation {
private static final String TAG = "CharacteristicWriteOp";
private BluetoothGatt gatt;
private BluetoothGattCharacteristic characteristic;
private byte[] value;
private Semaphore operationComplete = new Semaphore(0,true);
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.e(TAG,"Timeout waiting for gatt write operation to complete");
timedOut = true;
}
} catch (InterruptedException e) {
Log.e(TAG,"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.e(TAG,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();
}
@Override
public byte[] getValue() {
return value;
}
}

View file

@ -0,0 +1,56 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Created by geoff on 5/26/16.
*/
public class DescriptorWriteOperation extends BLECommOperation {
private static final String TAG = "DescrWriteOp";
private BluetoothGatt gatt;
private BluetoothGattDescriptor descr;
private byte[] value;
private Semaphore operationComplete = new Semaphore(0,true);
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.e(TAG,"Timeout waiting for descriptor write operation to complete");
timedOut = true;
}
} catch (InterruptedException e) {
Log.e(TAG,"Interrupted while waiting for descriptor write operation to complete");
interrupted = true;
}
}
}

View file

@ -0,0 +1,52 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import java.util.HashMap;
import java.util.UUID;
/**
* Created by geoff on 5/21/16.
*/
public class GattAttributes {
// NOTE: these uuid strings must be lower case!
private static HashMap<String, String> attributes = new HashMap();
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;
public static String CHARA_GAP_NUM = PREFIX + "2a01" + SUFFIX;
public static String SERVICE_BATTERY = PREFIX + "180f" + SUFFIX;
public static String CHARA_BATTERY_UNK = PREFIX + "2a19" + SUFFIX;
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";
// table of names for uuids
static {
attributes.put(SERVICE_GAP, "Device Information Service");
attributes.put(SERVICE_BATTERY,"Battery Service");
attributes.put(CHARA_RADIO_CUSTOM_NAME,"customName");
attributes.put(CHARA_RADIO_DATA,"data");
attributes.put(CHARA_RADIO_RESPONSE_COUNT,"responseCount");
attributes.put(CHARA_RADIO_TIMER_TICK,"timerTick");
attributes.put(CHARA_RADIO_VERSION,"radioVersion");
}
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;
}
}

View file

@ -0,0 +1,203 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperation;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.StringUtil;
import com.gxwtech.roundtrip2.util.ThreadUtil;
import java.util.UUID;
/**
* Created by geoff on 5/26/16.
*/
public class RFSpy {
public static final byte RFSPY_GET_STATE = 1;
public static final byte RFSPY_GET_VERSION = 2;
public static final byte RFSPY_GET_PACKET = 3; // aka Listen, receive
public static final byte RFSPY_SEND = 4;
public static final byte RFSPY_SEND_AND_LISTEN = 5;
public static final byte RFSPY_UPDATE_REGISTER = 6;
public static final byte RFSPY_RESET = 7;
public static final long RILEYLINK_FREQ_XTAL = 24000000;
public static final byte CC111X_REG_FREQ2 = 0x09;
public static final byte CC111X_REG_FREQ1 = 0x0A;
public static final byte CC111X_REG_FREQ0 = 0x0B;
public static final byte CC111X_MDMCFG4 = 0x0C;
public static final byte CC111X_MDMCFG3 = 0x0D;
public static final byte CC111X_MDMCFG2 = 0x0E;
public static final byte CC111X_MDMCFG1 = 0x0F;
public static final byte CC111X_MDMCFG0 = 0x10;
public static final byte CC111X_AGCCTRL2 = 0x17;
public static final byte CC111X_AGCCTRL1 = 0x18;
public static final byte CC111X_AGCCTRL0 = 0x19;
public static final byte CC111X_FREND1 = 0x1A;
public static final byte CC111X_FREND0 = 0x1B;
public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 1500;
private static final String TAG = "RFSpy";
private RileyLinkBLE rileyLinkBle;
private RFSpyReader reader;
private Context context;
UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION);
UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT);
private static final byte[] pumpID = {0x51, (byte)0x81, 0x63};
public RFSpy(Context context, RileyLinkBLE rileyLinkBle) {
this.context = context;
this.rileyLinkBle = rileyLinkBle;
reader = new RFSpyReader(context, rileyLinkBle);
}
// 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();
}
// 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) {
return StringUtil.fromBytes(result.value);
} else {
Log.e(TAG,"getVersion failed with code: "+result.resultCode);
return "(null)";
}
}
// The caller has to know how long the RFSpy will be busy with what was sent to it.
private RFSpyResponse writeToData(byte[] bytes, int responseTimeout_ms) {
SystemClock.sleep(100);
// FIXME drain read queue?
byte[] junkInBuffer = reader.poll(0);
while (junkInBuffer != null) {
Log.w(TAG, 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);
BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID,radioDataUUID,prepended);
if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
Log.e(TAG,"BLE Write operation failed, code=" + writeCheck.resultCode);
return new RFSpyResponse(); // 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);
RFSpyResponse resp = new RFSpyResponse(rawResponse);
if (rawResponse == null) {
Log.e(TAG,"writeToData: No response from RileyLink");
} else {
if (resp.wasInterrupted()) {
Log.e(TAG, "writeToData: RileyLink was interrupted");
} else if (resp.wasTimeout()) {
Log.e(TAG, "writeToData: RileyLink reports timeout");
} else if (resp.isOK()) {
Log.w(TAG, "writeToData: RileyLink reports OK");
} else {
if (resp.looksLikeRadioPacket()) {
RadioResponse radioResp = resp.getRadioResponse();
byte[] responsePayload = radioResp.getPayload();
Log.i(TAG,"writeToData: decoded radio response is "+ByteUtil.shortHexString(responsePayload));
}
//Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse));
}
}
return resp;
}
public RFSpyResponse getRadioVersion() {
RFSpyResponse resp = writeToData(new byte[] {RFSPY_GET_VERSION},1000);
if (resp == null) {
Log.e(TAG,"getRadioVersion returned null");
}
/*
Log.d(TAG,"checking response count");
BLECommOperationResult checkRC = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID,responseCountUUID);
if (checkRC.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
Log.d(TAG,"Response count is: " + ByteUtil.shortHexString(checkRC.value));
} else {
Log.e(TAG,"Error getting response count, code is " + checkRC.resultCode);
}
*/
return resp;
}
public RFSpyResponse transmit(RadioPacket radioPacket, byte sendChannel, byte repeatCount, byte delay_ms) {
// append checksum, encode data, send it.
byte[] fullPacket = ByteUtil.concat(new byte[] {RFSPY_SEND,sendChannel,repeatCount, delay_ms},radioPacket.getEncoded());
RFSpyResponse response = writeToData(fullPacket,repeatCount * delay_ms);
return response;
}
public RFSpyResponse receive(byte listenChannel, int timeout_ms, byte retryCount) {
int receiveDelay = timeout_ms * (retryCount+1);
byte[] listen = {RFSPY_GET_PACKET,listenChannel,
(byte)((timeout_ms >> 24)&0x0FF),
(byte)((timeout_ms >> 16)&0x0FF),
(byte)((timeout_ms >> 8)&0x0FF),
(byte)(timeout_ms & 0x0FF),
retryCount};
return writeToData(listen,receiveDelay);
}
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) {
int sendDelay = repeatCount * delay_ms;
int receiveDelay = timeout_ms * (retryCount + 1);
byte[] sendAndListen = {RFSPY_SEND_AND_LISTEN,sendChannel,repeatCount,delay_ms,listenChannel,
(byte)((timeout_ms >> 24)&0x0FF),
(byte)((timeout_ms >> 16)&0x0FF),
(byte)((timeout_ms >> 8)&0x0FF),
(byte)(timeout_ms & 0x0FF),
retryCount};
byte[] fullPacket = ByteUtil.concat(sendAndListen,pkt.getEncoded());
return writeToData(fullPacket, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
}
public RFSpyResponse updateRegister(byte addr, byte val) {
byte[] updateRegisterPkt = new byte[] {RFSPY_UPDATE_REGISTER,addr,val};
RFSpyResponse resp = writeToData(updateRegisterPkt,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(CC111X_REG_FREQ0, (byte)(value & 0xff));
updateRegister(CC111X_REG_FREQ1, (byte)((value >> 8) & 0xff));
updateRegister(CC111X_REG_FREQ2, (byte)((value >> 16) & 0xff));
Log.w(TAG,String.format("Set frequency to %.2f",freqMHz));
}
}

View file

@ -0,0 +1,117 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import android.content.Context;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.ThreadUtil;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Created by geoff on 5/26/16.
*/
public class RFSpyReader {
private static final String TAG = "RFSpyReader";
private Context context;
private RileyLinkBLE rileyLinkBle;
private Semaphore waitForRadioData = new Semaphore(0,true);
AsyncTask<Void,Void,Void> readerTask;
private LinkedBlockingQueue<byte[]> mDataQueue = new LinkedBlockingQueue<>();
private int acquireCount = 0;
private int releaseCount = 0;
public RFSpyReader(Context context, RileyLinkBLE rileyLinkBle) {
this.context = context;
this.rileyLinkBle = rileyLinkBle;
}
public void init(Context context, RileyLinkBLE rileyLinkBLE) {
this.context = context;
this.rileyLinkBle = rileyLinkBLE;
}
public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) {
if (readerTask != null) {
readerTask.cancel(true);
}
this.rileyLinkBle = rileyLinkBle;
}
// This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen.
public byte[] poll(int timeout_ms) {
Log.v(TAG, 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) {
Log.d(TAG, "Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t==" + SystemClock.uptimeMillis());
} else {
Log.d(TAG, "Got data [null] at t==" + SystemClock.uptimeMillis());
}
return dataFromQueue;
} catch (InterruptedException e) {
Log.e(TAG,"poll: Interrupted waiting for data");
}
return null;
}
// Call this from the "response count" notification handler.
public void newDataIsAvailable() {
releaseCount++;
Log.v(TAG,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();
Log.v(TAG,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) {
// 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.e(TAG, "Read operation was interrupted");
} else if (result.resultCode == BLECommOperationResult.RESULT_TIMEOUT) {
Log.e(TAG, "Read operation on Radio Data timed out");
} else if (result.resultCode == BLECommOperationResult.RESULT_BUSY) {
Log.e(TAG, "FAIL: RileyLinkBLE reports operation already in progress");
} else if (result.resultCode == BLECommOperationResult.RESULT_NONE) {
Log.e(TAG, "FAIL: got invalid result code: " + result.resultCode);
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for data");
}
}
}
}.execute();
}
}

View file

@ -0,0 +1,75 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
/**
* Created by geoff on 5/26/16.
*/
public class RFSpyResponse {
// 0xaa == timeout
// 0xbb == interrupted
// 0xcc == zero-data
protected byte[] raw;
protected RadioResponse radioResponse;
public RFSpyResponse() {
init(new byte[0]);
}
public RFSpyResponse(byte[] bytes) {
init(bytes);
}
public void init(byte[] bytes) {
if (bytes == null) {
raw = new byte[0];
} else {
raw = bytes;
}
if (looksLikeRadioPacket()) {
radioResponse = new RadioResponse(raw);
} else {
radioResponse = new RadioResponse();
}
}
public RadioResponse getRadioResponse() {
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 isOK() {
if ((raw.length == 1)||(raw.length==2)) {
if (raw[0] == (byte)0x01) {
return true;
}
}
return false;
}
public boolean looksLikeRadioPacket() {
if (raw.length > 2) {
return true;
}
return false;
}
public byte[] getRaw() {
return raw;
}
}

View file

@ -0,0 +1,295 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import android.util.Log;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.CRC;
import java.util.ArrayList;
/**
* Created by geoff on 7/31/15.
*/
public class RFTools {
private static final String TAG = "RFTools";
/*
CodeSymbols 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 byte[] CodeSymbols = {
0x15,
0x31,
0x32,
0x23,
0x34,
0x25,
0x26,
0x16,
0x1a,
0x19,
0x2a,
0x0b,
0x2c,
0x0d,
0x0e,
0x1c
};
public static byte[] appendChecksum(final byte[] input) {
if (input == null) {
return null;
}
if (input.length == 0) {
return null;
}
byte[] rval = new byte[input.length+1];
System.arraycopy(input, 0, rval, 0, input.length);
byte mycrc = CRC.crc8(input);
Log.d(TAG,String.format("Adding checksum 0x%02X to %d byte array from 0x%02X to 0x%02X",mycrc,input.length,input[0],input[input.length-1]));
rval[input.length] = mycrc;
return rval;
}
public static ArrayList<Byte> fromBytes(byte[] data) {
ArrayList<Byte> rval = new ArrayList<>();
for (int i=0; i<data.length; i++) {
rval.add(data[i]);
}
return rval;
}
public static byte[] toBytes(ArrayList<Byte> data) {
byte[] rval = new byte[data.size()];
for (int i = 0; i < data.size(); i++) {
rval[i] = data.get(i);
}
return rval;
}
/*
+ (NSData*)encode4b6b:(NSData*)data {
NSMutableData *outData = [NSMutableData data];
NSMutableData *dataPlusCrc = [data mutableCopy];
unsigned char crc = [MinimedPacket crcForData:data];
[dataPlusCrc appendBytes:&crc length:1];
char codes[16] = {21,49,50,35,52,37,38,22,26,25,42,11,44,13,14,28};
const unsigned char *inBytes = [dataPlusCrc bytes];
unsigned int acc = 0x0;
int bitcount = 0;
for (int i=0; i < dataPlusCrc.length; i++) {
acc <<= 6;
acc |= codes[inBytes[i] >> 4];
bitcount += 6;
acc <<= 6;
acc |= codes[inBytes[i] & 0x0f];
bitcount += 6;
while (bitcount >= 8) {
unsigned char outByte = acc >> (bitcount-8) & 0xff;
[outData appendBytes:&outByte length:1];
bitcount -= 8;
acc &= (0xffff >> (16-bitcount));
}
}
if (bitcount > 0) {
acc <<= (8-bitcount);
unsigned char outByte = acc & 0xff;
[outData appendBytes:&outByte length:1];
}
return outData;
}
*/
public static final byte[] codes = new byte[] {21,49,50,35,52,37,38,22,26,25,42,11,44,13,14,28 };
/* O(n) lookup. Run on an O(n) translation of a byte-stream, gives O(n**2) performance. Sigh. */
public static int codeIndex(byte b) {
for (int i=0; i< codes.length; i++) {
if (b == codes[i]) {
return i;
}
}
return -1;
}
public static byte[] encode4b6b(byte[] data) {
if ((data.length % 2)!=0) {
// Log.e(TAG,"Warning: data is odd number of bytes");
}
// use arraylists because byte[] is annoying.
ArrayList<Byte> inData = fromBytes(data);
ArrayList<Byte> outData = new ArrayList<>();
int acc = 0;
int bitcount = 0;
int i;
for (i=0; i<inData.size(); i++) {
acc <<= 6;
acc |= codes[(inData.get(i) >> 4) & 0x0f];
bitcount += 6;
acc <<= 6;
acc |= codes[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 = toBytes(outData);
return rval;
}
public static void test() {
/*
{0xa7} -> {0xa9, 0x60}
{0xa7, 0x12} -> {0xa9, 0x6c, 0x72}
{0xa7, 0x12, 0xa7} -> {0xa9, 0x6c, 0x72, 0xa9, 0x60}
*/
/* test compare */
byte[] s1 = {0,1,2};
byte[] s2 = {2,1,0,3};
byte[] s3 = {0,1,2,3};
if (ByteUtil.compare(s1,s1)!=0) {
Log.e(TAG,"test: compare failed.");
}
if (ByteUtil.compare(s1,s2)>=0) {
Log.e(TAG,"test: compare failed.");
}
if (ByteUtil.compare(s2,s1)<=0) {
Log.e(TAG,"test: compare failed.");
}
if (ByteUtil.compare(s1,s3)>=0) {
Log.e(TAG,"test: compare failed.");
}
//testCompose(new byte[] {(byte)0xa7, (byte)0xa7});
byte[] bs = encode4b6b(new byte[]{(byte) 0xa7});
byte[] out = new byte[] {(byte)(0xa9),0x65};
if (ByteUtil.compare(bs,out)!=0) {
Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs));
}
bs = encode4b6b(new byte[]{(byte) 0xa7, 0x12});
out = new byte[] {(byte)(0xa9),0x6c,0x72};
if (ByteUtil.compare(bs,out)!=0) {
Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs));
}
bs = encode4b6b(new byte[]{(byte) 0xa7, 0x12, (byte) 0xa7});
out = new byte[] {(byte)(0xa9),0x6c,0x72,(byte)0xa9,0x65};
if (ByteUtil.compare(bs,out)!=0) {
Log.e(TAG,"encode Data failed: expected "+ByteUtil.shortHexString(out)+" but got "+ByteUtil.shortHexString(bs));
}
return;
}
public static byte[] decode4b6b(byte[] raw) throws NumberFormatException {
/*
if ((raw.length % 2) != 0) {
Log.e(TAG,"Warning: data is 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 = codeIndex((byte) (highcode));
// take bottom six
int lowcode = (x >> (availableBits - 12)) & 0x3F;
int lowIndex = codeIndex((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.d(TAG,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.d(TAG,String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits));
codingErrors++;
}
availableBits -= 12;
x = x & (0x0000ffff >> (16 - availableBits));
} else {
//Log.d(TAG,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.e(TAG, "decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + ")");
codingErrors++;
}
} else {
// also normal end.
}
if (codingErrors>0) {
Log.e(TAG, "decode4b6b: "+codingErrors+" coding errors encountered.");
throw new NumberFormatException();
}
return rval;
}
public static String toHexString(byte[] array) {
return toHexString(array, 0, array.length);
}
private final static char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
public static String toHexString(byte[] array, int offset, int length) {
char[] buf = new char[length * 2];
int bufIndex = 0;
for (int i = offset; i < offset + length; i++) {
byte b = array[i];
buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
}
return new String(buf);
}
}

View file

@ -0,0 +1,24 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.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[] getEncoded() {
byte[] withCRC = ByteUtil.concat(pkt, CRC.crc8(pkt));
byte[] encoded = RFTools.encode4b6b(withCRC);
byte[] withNullTerm = ByteUtil.concat(encoded,(byte)0);
return withNullTerm;
}
}

View file

@ -0,0 +1,67 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
import android.util.Log;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.CRC;
/**
* Created by geoff on 5/30/16.
*/
public class RadioResponse {
private static final String TAG = "RadioResponse";
public boolean decodedOK = false;
public int rssi;
public int responseNumber;
public byte[] decodedPayload = new byte[0];
public byte receivedCRC;
public RadioResponse() {
}
public RadioResponse(byte[] rxData) {
init(rxData);
}
public boolean isValid() {
if (!decodedOK) {
return false;
}
if (decodedPayload != null) {
if (receivedCRC == CRC.crc8(decodedPayload)) {
return true;
}
}
return false;
}
public void init(byte[] rxData) {
if (rxData == null) {
return;
}
if (rxData.length < 3) {
// This does not look like something valid heard from a RileyLink device
return;
}
rssi = rxData[0];
responseNumber = rxData[1];
byte[] encodedPayload = ByteUtil.substring(rxData, 2, rxData.length - 2);
try {
byte[] decodeThis = RFTools.decode4b6b(encodedPayload);
decodedOK = true;
decodedPayload = ByteUtil.substring(decodeThis, 0, decodeThis.length - 1);
byte calculatedCRC = CRC.crc8(decodedPayload);
receivedCRC = decodeThis[decodeThis.length - 1];
if (receivedCRC != calculatedCRC) {
Log.e(TAG, String.format("RadioResponse: CRC mismatch, calculated 0x%02x, received 0x%02x", calculatedCRC, receivedCRC));
}
} catch (NumberFormatException e) {
decodedOK = false;
Log.e(TAG, "Failed to decode radio data: " + ByteUtil.shortHexString(encodedPayload));
}
}
public byte[] getPayload() {
return decodedPayload;
}
}

View file

@ -0,0 +1,428 @@
package com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE;
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.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperation;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.BLECommOperationResult;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.CharacteristicReadOperation;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.CharacteristicWriteOperation;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.BLECommOperations.DescriptorWriteOperation;
import com.gxwtech.roundtrip2.util.HexDump;
import com.gxwtech.roundtrip2.util.ThreadUtil;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Semaphore;
/**
* Created by geoff on 5/26/16.
*/
public class RileyLinkBLE {
private static final String TAG = "RileyLinkBLE";
public boolean gattDebugEnabled = true;
private BluetoothAdapter bluetoothAdapter;
private BluetoothGattCallback bluetoothGattCallback;
private final Context context;
private BluetoothManager bluetoothManager;
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.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
this.bluetoothAdapter = bluetoothManager.getAdapter();
bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if (gattDebugEnabled) {
Log.v(TAG, ThreadUtil.sig() + "onCharacteristicChanged " + GattAttributes.lookup(characteristic.getUuid()) + " " + HexDump.toHexString(characteristic.getValue()));
if (characteristic.getUuid().equals(UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT))) {
Log.d(TAG, "Response Count is " + HexDump.toHexString(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) {
Log.v(TAG, ThreadUtil.sig() + "onCharacteristicRead (" + GattAttributes.lookup(characteristic.getUuid()) + ") "
+ statusMessage + ":" + HexDump.toHexString(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) {
Log.v(TAG, ThreadUtil.sig() + "onCharacteristicWrite " + getGattStatusMessage(status) + " " + uuidString + " " + HexDump.toHexString(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.e(TAG, "Got the status 133 bug, closing gatt");
disconnect();
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 + ")";
}
Log.w(TAG, "onConnectionStateChange " + getGattStatusMessage(status) + " " + stateMessage);
}
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.bluetooth_connected));
} else {
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.bluetooth_disconnected));
disconnect();
Log.w(TAG, "Cannot establish Bluetooth connection.");
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
if (gattDebugEnabled) {
Log.w(TAG, "onDescriptorWrite "
+ GattAttributes.lookup(descriptor.getUuid()) + " "
+ getGattStatusMessage(status)
+ " written: " + HexDump.toHexString(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) {
Log.w(TAG, "onDescriptorRead " + getGattStatusMessage(status) + " status " + descriptor);
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
if (gattDebugEnabled) {
Log.w(TAG, "onMtuChanged " + mtu + " status " + status);
}
}
@Override
public void onReadRemoteRssi(final BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
if (gattDebugEnabled) {
Log.w(TAG, "onReadRemoteRssi " + getGattStatusMessage(status) + ": " + rssi);
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
if (gattDebugEnabled) {
Log.w(TAG, "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();
// TODO: in here, we need to determine if this Bluetooth device is a RileyLink with appropriate software (subg_rfspy) and set mIsConnected if the GATT is ok.
for (BluetoothGattService service : services) {
final List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
final UUID uuidService = service.getUuid();
final String uuidServiceString = uuidService.toString();
if (gattDebugEnabled) {
String debugString = "Found service: " + GattAttributes.lookup(uuidServiceString, "Unknown device") + " (" + uuidServiceString + ")";
for (BluetoothGattCharacteristic character : characteristics) {
final String uuidCharacteristicString = character.getUuid().toString();
debugString += " - " + GattAttributes.lookup(uuidCharacteristicString);
}
Log.w(TAG, debugString);
}
}
if (gattDebugEnabled) {
Log.w(TAG, "onServicesDiscovered " + getGattStatusMessage(status));
}
mIsConnected = true;
Intent intent = new Intent(RT2Const.serviceLocal.BLE_services_discovered);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
} else {
Log.e(TAG, "onServicesDiscovered " + getGattStatusMessage(status));
}
}
};
}
public void registerRadioResponseCountNotification(Runnable notifier) {
radioResponseCountNotified = notifier;
}
public boolean isConnected() {
return mIsConnected;
}
public void discoverServices() {
if (bluetoothConnectionGatt.discoverServices()) {
Log.w(TAG, "Starting to discover GATT Services.");
} else {
Log.e(TAG, "Cannot discover GATT Services.");
}
}
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.e(TAG, "Error setting response count notification");
return false;
}
return true;
}
public void findRileyLink(String RileyLinkAddress) {
Log.d(TAG,"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);
if (bluetoothConnectionGatt == null) {
Log.e(TAG,"Failed to connect to Bluetooth Low Energy device at "+bluetoothAdapter.getAddress());
Toast.makeText(context, "No Rileylink at " + bluetoothAdapter.getAddress(), Toast.LENGTH_SHORT).show();
} else {
if (gattDebugEnabled) {
Log.d(TAG, "Gatt Connected?");
}
}
}
public void disconnect() {
mIsConnected = false;
Log.w(TAG, "Closing GATT connection");
// Close old conenction
if (bluetoothConnectionGatt != null) {
// Not sure if to disconnect or to close first..
bluetoothConnectionGatt.disconnect();
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.e(TAG, "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.e(TAG, "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++) {
Log.d(TAG, "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.e(TAG,"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.e(TAG,"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.e(TAG, "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.e(TAG,"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.e(TAG, "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.e(TAG,"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;
}
}

View file

@ -0,0 +1,638 @@
package com.gxwtech.roundtrip2.RoundtripService;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcel;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManager;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RFSpy;
import com.gxwtech.roundtrip2.RoundtripService.RileyLinkBLE.RileyLinkBLE;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.DiscoverGattServicesTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.FetchPumpHistoryTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.InitializePumpManagerTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadBolusWizardCarbProfileTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadISFProfileTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.ReadPumpClockTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.RetrieveHistoryPageTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.ServiceTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.ServiceTaskExecutor;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.UpdatePumpStatusTask;
import com.gxwtech.roundtrip2.RoundtripService.Tasks.WakeAndTuneTask;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.PumpHistoryManager;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat;
import com.gxwtech.roundtrip2.ServiceData.ServiceNotification;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
import com.gxwtech.roundtrip2.util.ByteUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
/**
* RoundtripService is intended to stay running when the gui-app is closed.
*/
public class RoundtripService extends Service {
private static final String TAG="RoundtripService";
private static RoundtripService instance;
private static final String WAKELOCKNAME = "com.gxwtech.roundtrip2.RoundtripServiceWakeLock";
private static volatile PowerManager.WakeLock lockStatic = null;
private boolean needBluetoothPermission = true;
private BroadcastReceiver mBroadcastReceiver;
private Context mContext;
private RoundtripServiceIPCConnection serviceConnection;
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
// saved settings
public SharedPreferences sharedPref;
private String pumpIDString;
private byte[] pumpIDBytes;
private String mRileylinkAddress;
// cache of most recently received set of pump history pages. Probably shouldn't be here.
ArrayList<Page> mHistoryPages;
PumpHistoryManager pumpHistoryManager;
// Our hardware/software connection
public RileyLinkBLE rileyLinkBLE; // android-bluetooth management
private RFSpy rfspy; // interface for 916MHz radio.
public PumpManager pumpManager; // interface to Minimed
private static ServiceTask currentTask = null;
public RoundtripService() {
super();
instance = this;
Log.d(TAG, "RoundtripService newly constructed");
}
public static RoundtripService getInstance() {
return instance;
}
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
mContext = getApplicationContext();
serviceConnection = new RoundtripServiceIPCConnection(mContext);
//sharedPref = mContext.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE);
sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
// get most recently used pumpID
pumpIDString = sharedPref.getString(RT2Const.serviceLocal.pumpIDKey,"000000");
pumpIDBytes = ByteUtil.fromHexString(pumpIDString);
if (pumpIDBytes == null) {
Log.e(TAG,"Invalid pump ID? " + ByteUtil.shortHexString(pumpIDBytes));
pumpIDBytes = new byte[] {0,0,0};
pumpIDString = "000000";
}
if (pumpIDBytes.length != 3) {
Log.e(TAG,"Invalid pump ID? " + ByteUtil.shortHexString(pumpIDBytes));
pumpIDBytes = new byte[] {0,0,0};
pumpIDString = "000000";
}
if (pumpIDString.equals("000000")) {
Log.e(TAG,"Using pump ID "+pumpIDString);
} else {
Log.i(TAG,"Using pump ID "+pumpIDString);
}
// get most recently used RileyLink address
mRileylinkAddress = sharedPref.getString(RT2Const.serviceLocal.rileylinkAddressKey,"");
pumpHistoryManager = new PumpHistoryManager(getApplicationContext());
rileyLinkBLE = new RileyLinkBLE(this);
rfspy = new RFSpy(mContext,rileyLinkBLE);
rfspy.startReader();
pumpManager = new PumpManager(mContext,rfspy,pumpIDBytes);
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
/* here we can listen for local broadcasts, then send ourselves
* a specific intent to deal with them, if we wish
*/
if (intent == null) {
Log.e(TAG,"onReceive: received null intent");
} else {
String action = intent.getAction();
if (action == null) {
Log.e(TAG,"onReceive: null action");
} else {
if (action.equals(RT2Const.serviceLocal.bluetooth_connected)) {
Log.w(TAG,"serviceLocal.bluetooth_connected");
serviceConnection.sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_FindingRileyLink),null);
ServiceTaskExecutor.startTask(new DiscoverGattServicesTask());
// If this is successful,
// We will get a broadcast of RT2Const.serviceLocal.BLE_services_discovered
} else if (action.equals(RT2Const.serviceLocal.BLE_services_discovered)) {
Log.w(TAG,"serviceLocal.BLE_services_discovered");
serviceConnection.sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_WakingPump),null);
rileyLinkBLE.enableNotifications();
rfspy.startReader(); // call startReader from outside?
ServiceTask task = new InitializePumpManagerTask();
ServiceTaskExecutor.startTask(task);
Log.i(TAG, "Announcing RileyLink open For business");
} else if (action.equals(RT2Const.serviceLocal.ipcBound)) {
// If we still need permission for bluetooth, ask now.
if (needBluetoothPermission) {
sendBLERequestForAccess();
}
} else if (RT2Const.IPC.MSG_BLE_accessGranted.equals(action)) {
//initializeLeAdapter();
//BluetoothInit();
} else if (RT2Const.IPC.MSG_BLE_accessDenied.equals(action)) {
stopSelf(); // This will stop the service.
} else if (action.equals(RT2Const.IPC.MSG_PUMP_tunePump)) {
doTunePump();
} else if (action.equals(RT2Const.IPC.MSG_PUMP_quickTune)) {
doTunePump();
} else if (action.equals(RT2Const.IPC.MSG_PUMP_fetchHistory)) {
mHistoryPages = pumpManager.getAllHistoryPages();
final boolean savePages = true;
if (savePages) {
for (int i = 0; i < mHistoryPages.size(); i++) {
String filename = "PumpHistoryPage-" + i;
Log.w(TAG, "Saving history page to file " + filename);
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, 0);
byte[] rawData= mHistoryPages.get(i).getRawData();
if (rawData != null) {
outputStream.write(rawData);
}
outputStream.close();
} catch (FileNotFoundException fnf) {
fnf.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC, 0, 0);
// Create a bundle with the data
Bundle bundle = new Bundle();
bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_PUMP_history);
ArrayList<Bundle> packedPages = new ArrayList<>();
for (Page page : mHistoryPages) {
packedPages.add(page.pack());
}
bundle.putParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key, packedPages);
// save it to SQL.
pumpHistoryManager.clearDatabase();
pumpHistoryManager.initFromPages(bundle);
// write html page to documents folder
pumpHistoryManager.writeHtmlPage();
// Set payload
msg.setData(bundle);
serviceConnection.sendMessage(msg,null/*broadcast*/);
Log.d(TAG, "sendMessage: sent Full history report");
} else if (RT2Const.IPC.MSG_PUMP_fetchSavedHistory.equals(action)) {
Log.i(TAG,"Fetching saved history");
FileInputStream inputStream;
ArrayList<Page> storedHistoryPages = new ArrayList<>();
for (int i = 0; i < 16; i++) {
String filename = "PumpHistoryPage-" + i;
try {
inputStream = openFileInput(filename);
byte[] buffer = new byte[1024];
int numRead = inputStream.read(buffer, 0, 1024);
if (numRead == 1024) {
Page p = new Page();
//p.parseFrom(buffer, PumpModel.MM522);
p.parseFrom(buffer, PumpModel.MM522);
storedHistoryPages.add(p);
} else {
Log.e(TAG, filename + " error: short file");
}
} catch (FileNotFoundException fnf) {
Log.e(TAG, "Failed to open " + filename + " for reading.");
} catch (IOException e) {
Log.e(TAG, "Failed to read " + filename);
} catch (Exception e) {
e.printStackTrace();
}
}
mHistoryPages = storedHistoryPages;
if (storedHistoryPages.isEmpty()) {
Log.e(TAG, "No stored history pages loaded");
} else {
Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC, 0, 0);
// Create a bundle with the data
Bundle bundle = new Bundle();
bundle.putString(RT2Const.IPC.messageKey, RT2Const.IPC.MSG_PUMP_history);
ArrayList<Bundle> packedPages = new ArrayList<>();
for (Page page : mHistoryPages) {
packedPages.add(page.pack());
}
bundle.putParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key, packedPages);
// save it to SQL.
pumpHistoryManager.clearDatabase();
pumpHistoryManager.initFromPages(bundle);
// write html page to documents folder
pumpHistoryManager.writeHtmlPage();
// Set payload
msg.setData(bundle);
serviceConnection.sendMessage(msg,null/*broadcast*/);
}
} else if (RT2Const.IPC.MSG_ServiceCommand.equals(action)) {
Bundle bundle = intent.getBundleExtra(RT2Const.IPC.bundleKey);
handleIncomingServiceTransport(new ServiceTransport(bundle));
} else if (RT2Const.serviceLocal.INTENT_sessionCompleted.equals(action)) {
Bundle bundle = intent.getBundleExtra(RT2Const.IPC.bundleKey);
if (bundle != null) {
ServiceTransport transport = new ServiceTransport(bundle);
serviceConnection.sendTransport(transport, transport.getSenderHashcode());
} else {
Log.e(TAG,"sessionCompleted: no bundle!");
}
} else {
Log.e(TAG, "Unhandled broadcast: action=" + action);
}
}
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(RT2Const.serviceLocal.bluetooth_connected);
intentFilter.addAction(RT2Const.serviceLocal.bluetooth_disconnected);
intentFilter.addAction(RT2Const.serviceLocal.BLE_services_discovered);
intentFilter.addAction(RT2Const.serviceLocal.ipcBound);
intentFilter.addAction(RT2Const.IPC.MSG_BLE_accessGranted);
intentFilter.addAction(RT2Const.IPC.MSG_BLE_accessDenied);
intentFilter.addAction(RT2Const.IPC.MSG_BLE_useThisDevice);
intentFilter.addAction(RT2Const.IPC.MSG_PUMP_tunePump);
intentFilter.addAction(RT2Const.IPC.MSG_PUMP_fetchHistory);
intentFilter.addAction(RT2Const.IPC.MSG_PUMP_useThisAddress);
intentFilter.addAction(RT2Const.IPC.MSG_PUMP_fetchSavedHistory);
intentFilter.addAction(RT2Const.IPC.MSG_ServiceCommand);
intentFilter.addAction(RT2Const.serviceLocal.INTENT_sessionCompleted);
LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, intentFilter);
Log.d(TAG, "onCreate(): It's ALIVE!");
}
@Override
public boolean onUnbind(Intent intent) {
Log.w(TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.w(TAG,"onRebind");
super.onRebind(intent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.w(TAG,"onConfigurationChanged");
super.onConfigurationChanged(newConfig);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return serviceConnection.doOnBind(intent);
}
// 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) {
Log.d(TAG, "onStartCommand");
if (intent != null) {
PowerManager.WakeLock lock = getLock(this.getApplicationContext());
if (!lock.isHeld() || (flags & START_FLAG_REDELIVERY) != 0) {
lock.acquire();
}
// This will end up running onHandleIntent
super.onStartCommand(intent, flags, startId);
} else {
Log.e(TAG, "Received null intent?");
}
BluetoothInit(); // this kicks off our process of device discovery.
return (START_REDELIVER_INTENT | START_STICKY);
}
synchronized private static PowerManager.WakeLock getLock(Context context) {
if (lockStatic == null) {
PowerManager mgr =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
lockStatic = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCKNAME);
lockStatic.setReferenceCounted(true);
}
return lockStatic;
}
// FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc.
private void doTunePump() {
double lastGoodFrequency = sharedPref.getFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency,(float)0.0);
double newFrequency;
if (lastGoodFrequency != 0.0) {
Log.i(TAG,String.format("Checking for pump near last saved frequency of %.2fMHz",lastGoodFrequency));
// we have an old frequency, so let's start there.
newFrequency = pumpManager.quickTuneForPump(lastGoodFrequency);
if (newFrequency == 0.0) {
// quick scan failed to find pump. Try full scan
Log.w(TAG,String.format("Failed to find pump near last saved frequency, doing full scan"));
newFrequency = pumpManager.tuneForPump();
}
} else {
Log.w(TAG,"No saved frequency for pump, doing full scan.");
// we don't have a saved frequency, so do the full scan.
newFrequency = pumpManager.tuneForPump();
}
if ((newFrequency!=0.0) && (newFrequency != lastGoodFrequency)) {
Log.i(TAG,String.format("Saving new pump frequency of %.2fMHz",newFrequency));
SharedPreferences.Editor ed = sharedPref.edit();
ed.putFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency, (float)newFrequency);
ed.apply();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "I die! I die!");
}
/* private functions */
void BluetoothInit() {
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
}
}
// 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())) {
sendBLERequestForAccess();
} else {
needBluetoothPermission = false;
initializeLeAdapter();
}
}
public boolean initializeLeAdapter() {
Log.d(TAG,"initializeLeAdapter: attempting to get an adapter");
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
} else if (!mBluetoothAdapter.isEnabled()) {
// NOTE: This does not work!
Log.e(TAG, "Bluetooth is not enabled.");
}
return true;
}
private void setPumpIDString(String idString) {
if (idString.length() != 6) {
Log.e(TAG,"setPumpIDString: invalid pump id string: " + idString);
}
pumpIDString = idString;
pumpIDBytes = ByteUtil.fromHexString(pumpIDString);
SharedPreferences prefs = mContext.getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(RT2Const.serviceLocal.pumpIDKey,pumpIDString);
editor.apply();
Log.i(TAG,"setPumpIDString: saved pumpID "+pumpIDString);
}
private void sendBLERequestForAccess() {
//serviceConnection.sendMessage(RT2Const.IPC.MSG_BLE_requestAccess);
}
private void reportPumpFound() {
//serviceConnection.sendMessage(RT2Const.IPC.MSG_PUMP_pumpFound);
}
public void setCurrentTask(ServiceTask task) {
if (currentTask == null) {
currentTask = task;
} else {
Log.e(TAG,"setCurrentTask: Cannot replace current task");
}
}
public void finishCurrentTask(ServiceTask task) {
if (task != currentTask) {
Log.e(TAG,"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;
}
private void handleIncomingServiceTransport(ServiceTransport serviceTransport) {
if (serviceTransport.getServiceCommand().isPumpCommand()) {
switch (serviceTransport.getOriginalCommandName()) {
case "ReadPumpClock":
ServiceTaskExecutor.startTask(new ReadPumpClockTask(serviceTransport));
break;
case "FetchPumpHistory":
ServiceTaskExecutor.startTask(new FetchPumpHistoryTask(serviceTransport));
break;
case "RetrieveHistoryPage":
ServiceTask task = new RetrieveHistoryPageTask(serviceTransport);
ServiceTaskExecutor.startTask(task);
break;
case "ReadISFProfile":
ServiceTaskExecutor.startTask(new ReadISFProfileTask(serviceTransport));
/*
ISFTable table = pumpManager.getPumpISFProfile();
ServiceResult result = new ServiceResult();
if (table.isValid()) {
// convert from ISFTable to ISFProfile
Bundle map = result.getMap();
map.putIntArray("times", table.getTimes());
map.putFloatArray("rates", table.getRates());
map.putString("ValidDate", TimeFormat.standardFormatter().print(table.getValidDate()));
result.setMap(map);
result.setResultOK();
}
sendServiceTransportResponse(serviceTransport,result);
*/
break;
case "ReadBolusWizardCarbProfile":
ServiceTaskExecutor.startTask(new ReadBolusWizardCarbProfileTask());
break;
case "UpdatePumpStatus":
ServiceTaskExecutor.startTask(new UpdatePumpStatusTask());
break;
case "WakeAndTune":
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
default:
Log.e(TAG,"Failed to handle pump command: " + serviceTransport.getOriginalCommandName());
break;
}
} else {
switch (serviceTransport.getOriginalCommandName()) {
case "SetPumpID":
// This one is a command to RoundtripService, not to the PumpManager
String pumpID = serviceTransport.getServiceCommand().getMap().getString("pumpID", "");
ServiceResult result = new ServiceResult();
if ((pumpID != null) && (pumpID.length() == 6)) {
setPumpIDString(pumpID);
result.setResultOK();
} else {
Log.e(TAG, "handleIncomingServiceTransport: SetPumpID bundle missing 'pumpID' value");
result.setResultError(-1, "Invalid parameter (missing pumpID)");
}
sendServiceTransportResponse(serviceTransport, result);
break;
case "UseThisRileylink":
// If we are not connected, connect using the given address.
// If we are connected and the addresses differ, disconnect, connect to new.
// If we are connected and the addresses are the same, ignore.
String deviceAddress = serviceTransport.getServiceCommand().getMap().getString("rlAddress", "");
if ("".equals(deviceAddress)) {
Log.e(TAG, "handleIPCMessage: null RL address passed");
} else {
reconfigureRileylink(deviceAddress);
}
break;
default:
Log.e(TAG, "handleIncomingServiceTransport: Failed to handle service command '" + serviceTransport.getOriginalCommandName() + "'");
break;
}
}
}
// returns true if our Rileylink configuration changed
public boolean reconfigureRileylink(String deviceAddress) {
if (rileyLinkBLE.isConnected()) {
if (deviceAddress.equals(mRileylinkAddress)) {
Log.i(TAG, "No change to RL address. Not reconnecting.");
return false;
} else {
Log.w(TAG, "Disconnecting from old RL (" + mRileylinkAddress + "), reconnecting to new: " + deviceAddress);
rileyLinkBLE.disconnect();
// prolly need to shut down listening thread too?
SharedPreferences.Editor ed = sharedPref.edit();
ed.putString("rlAddress", deviceAddress);
ed.apply();
mRileylinkAddress = deviceAddress;
rileyLinkBLE.findRileyLink(mRileylinkAddress);
return true;
}
} else {
Toast.makeText(mContext, "Using RL " + deviceAddress, Toast.LENGTH_SHORT).show();
Log.d(TAG, "handleIPCMessage: Using RL " + deviceAddress);
if (mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
if (mBluetoothAdapter != null) {
if (mBluetoothAdapter.isEnabled()) {
// FIXME: this may be a long running function:
rileyLinkBLE.findRileyLink(deviceAddress);
// If successful, we will get a broadcast from RileyLinkBLE: RT2Const.serviceLocal.bluetooth_connected
return true;
} else {
Log.e(TAG, "Bluetooth is not enabled.");
return false;
}
} else {
Log.e(TAG, "Failed to get adapter");
return false;
}
}
}
public void announceProgress(int progressPercent) {
if (currentTask != null) {
ServiceNotification note = new ServiceNotification(RT2Const.IPC.MSG_note_TaskProgress);
note.getMap().putInt("progress",progressPercent);
note.getMap().putString("task",currentTask.getServiceTransport().getOriginalCommandName());
Integer senderHashcode = currentTask.getServiceTransport().getSenderHashcode();
serviceConnection.sendNotification(note, senderHashcode);
} else {
Log.e(TAG,"announceProgress: No current task");
}
}
public 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);
transport.setTransportType(RT2Const.IPC.MSG_ServiceResult);
serviceConnection.sendTransport(transport,clientHashcode);
}
public boolean sendNotification(ServiceNotification notification, Integer clientHashcode) {
return serviceConnection.sendNotification(notification, clientHashcode);
}
public void saveHistoryPage(int pagenumber, Page page) {
if ((page == null) || (page.getRawData() == null)) {
return;
}
String filename = "history-" + pagenumber;
FileOutputStream os;
try {
os = openFileOutput(filename, Context.MODE_PRIVATE);
os.write(page.getRawData());
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,165 @@
package com.gxwtech.roundtrip2.RoundtripService;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.ServiceData.ServiceNotification;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
import java.util.HashMap;
/**
* Created by geoff on 6/11/16.
*/
public class RoundtripServiceIPCConnection {
private static final String TAG = "RTServiceIPC";
private Context context;
//private ArrayList<Messenger> mClients = new ArrayList<>();
private HashMap<Integer,Messenger> mClients = new HashMap<>();
private final Messenger mMessenger = new Messenger(new IncomingHandler());
public RoundtripServiceIPCConnection(Context context) {
this.context = context;
}
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage: Received message " + msg);
Bundle bundle = msg.getData();
switch(msg.what) {
// This just helps sub-divide the message processing
case RT2Const.IPC.MSG_registerClient:
// send a reply, to let them know we're listening.
Message myReply = Message.obtain(null, RT2Const.IPC.MSG_clientRegistered,0,0);
try {
msg.replyTo.send(myReply);
mClients.put(mClients.hashCode(),msg.replyTo);
Log.v(TAG,"handleMessage: Registered client");
} catch (RemoteException e) {
// I guess they aren't registered after all...
Log.e(TAG,"handleMessage: failed to send acknowledgement of registration");
}
break;
case RT2Const.IPC.MSG_unregisterClient:
Log.v(TAG,"Unregistered client");
mClients.remove(msg.replyTo.hashCode());
break;
case RT2Const.IPC.MSG_IPC:
// As the current thread is likely a GUI thread from some app,
// rebroadcast the message as a local item.
// Convert from Message to Intent
if (msg.replyTo != null) {
try {
ServiceTransport transport = new ServiceTransport(bundle);
Log.d(TAG, "Service received IPC message" + transport.describeContentsShort());
transport.setSenderHashcode(msg.replyTo.hashCode());
Intent intent = new Intent(transport.getTransportType());
intent.putExtra(RT2Const.IPC.bundleKey, transport.getMap());
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
} catch (IllegalArgumentException e) {
// This can happen on screen tilts... what else is wrong?
Log.e(TAG,"Malformed service bundle: " + bundle.toString());
}
}
break;
/*
case RT2Const.MSG_ping:
String hello = (String)bundle.get("key_hello");
Toast.makeText(mContext,hello, Toast.LENGTH_SHORT).show();
break;
*/
default:
Log.e(TAG,"handleMessage: unknown 'what' in message: "+msg.what);
super.handleMessage(msg);
}
}
}
public IBinder doOnBind(Intent intent) {
Log.d(TAG, "onBind");
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(RT2Const.serviceLocal.ipcBound));
return mMessenger.getBinder();
}
public boolean sendNotification(ServiceNotification notification, Integer clientHashcode) {
ServiceTransport transport = new ServiceTransport();
transport.setServiceNotification(notification);
transport.setTransportType(RT2Const.IPC.MSG_ServiceNotification);
return sendTransport(transport,clientHashcode);
}
// if clientHashcode is null, broadcast to all clients
public boolean sendMessage(Message msg, Integer clientHashcode) {
Messenger clientMessenger = null;
if (mClients.isEmpty()) {
if (msg.what == RT2Const.IPC.MSG_IPC) {
Log.e(TAG, "sendMessage: no clients, cannot send: " + msg.getData().getString(RT2Const.IPC.messageKey, "(unknown)"));
} else {
Log.e(TAG, "sendMessage: no clients, cannot send: what=" + msg.what);
}
} else {
if (clientHashcode != null) {
clientMessenger = mClients.get(clientHashcode);
}
try {
if (clientMessenger != null) {
// sending to just one client
clientMessenger.send(msg);
} else {
// send to all clients
for (Integer clientHash : mClients.keySet()) {
Message m2 = Message.obtain(msg);
mClients.get(clientHash).send(m2);
}
}
} catch (RemoteException e) {
e.printStackTrace();
}
/*
catch (IllegalStateException e) {
// This happens every time we are in a bluetooth operation and the screen is turned.
Log.e(TAG,"sendMessage: IllegalStateException");
}
*/
}
return true;
}
/*
public void sendMessage(String messageType) {
Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC,0,0);
// Create a bundle with the data
Bundle bundle = new Bundle();
bundle.putString(RT2Const.IPC.messageKey, messageType);
// Set payload
msg.setData(bundle);
sendMessage(msg, null);
Log.d(TAG,"sendMessage: sent "+messageType);
}
*/
public boolean sendTransport(ServiceTransport transport, Integer clientHashcode) {
Message msg = Message.obtain(null, RT2Const.IPC.MSG_IPC,0,0);
// Set payload
msg.setData(transport.getMap());
Log.d(TAG,"Service sending message to " + String.valueOf(clientHashcode) + ": " + transport.describeContentsShort());
if ((clientHashcode != null) && (clientHashcode == 0)) {
return sendMessage(msg, null);
}
return sendMessage(msg, clientHashcode);
}
}

View file

@ -0,0 +1,15 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
/**
* Created by geoff on 7/9/16.
*/
public class DiscoverGattServicesTask extends ServiceTask {
public DiscoverGattServicesTask() {}
@Override
public void run() {
RoundtripService.getInstance().rileyLinkBLE.discoverServices();
}
}

View file

@ -0,0 +1,40 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page;
import com.gxwtech.roundtrip2.ServiceData.FetchPumpHistoryResult;
import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
import java.net.MalformedURLException;
import java.util.ArrayList;
/**
* Created by geoff on 7/16/16.
*/
public class FetchPumpHistoryTask extends PumpTask {
public FetchPumpHistoryTask() { }
public FetchPumpHistoryTask(ServiceTransport transport) {
super(transport);
}
private FetchPumpHistoryResult result = new FetchPumpHistoryResult();
@Override
public void run() {
ArrayList<Page> ra = new ArrayList<>();
for (int i=0; i<16; i++) {
Page page = RoundtripService.getInstance().pumpManager.getPumpHistoryPage(i);
if (page != null) {
ra.add(page);
RoundtripService.getInstance().saveHistoryPage(i,page);
}
}
result.setMap(getServiceTransport().getServiceResult().getMap());
result.setResultOK();
result.setPageArray(ra);
getServiceTransport().setServiceResult(result);
}
}

View file

@ -0,0 +1,42 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManager;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.ServiceData.ServiceNotification;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/9/16.
*
* 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";
public InitializePumpManagerTask() { super(); }
public InitializePumpManagerTask(ServiceTransport transport) { super(transport); }
@Override
public void run() {
SharedPreferences sharedPref = RoundtripService.getInstance().getApplicationContext().getSharedPreferences(RT2Const.serviceLocal.sharedPreferencesKey, Context.MODE_PRIVATE);
double lastGoodFrequency = sharedPref.getFloat(RT2Const.serviceLocal.prefsLastGoodPumpFrequency,(float)0.0);
if (lastGoodFrequency != 0) {
Log.i(TAG,String.format("Setting radio frequency to %.2fMHz",lastGoodFrequency));
RoundtripService.getInstance().pumpManager.setRadioFrequencyForPump(lastGoodFrequency);
}
PumpModel reportedPumpModel = RoundtripService.getInstance().pumpManager.getPumpModel();
if (!reportedPumpModel.equals(PumpModel.UNSET)) {
RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_PUMP_pumpFound),null);
} else {
RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_PUMP_pumpLost),null);
}
RoundtripService.getInstance().sendNotification(new ServiceNotification(RT2Const.IPC.MSG_note_Idle),null);
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/10/16.
*/
public class PumpTask extends ServiceTask {
public PumpTask() {super();}
public PumpTask(ServiceTransport transport) {
super(transport);
}
}

View file

@ -0,0 +1,24 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpMessage;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/10/16.
*/
public class ReadBolusWizardCarbProfileTask extends PumpTask {
public ReadBolusWizardCarbProfileTask() { super(); }
public ReadBolusWizardCarbProfileTask(ServiceTransport transport) {
super(transport);
}
@Override
public void run() {
PumpMessage msg = RoundtripService.getInstance().pumpManager.getBolusWizardCarbProfile();
ServiceResult result = getServiceTransport().getServiceResult();
// interpret msg here.
getServiceTransport().setServiceResult(result);
}
}

View file

@ -0,0 +1,37 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.ISFTable;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/10/16.
*/
public class ReadISFProfileTask extends PumpTask {
public ReadISFProfileTask() {}
public ReadISFProfileTask(ServiceTransport transport) { super(transport); }
@Override
public void preOp() {
}
@Override
public void run() {
ISFTable table = RoundtripService.getInstance().pumpManager.getPumpISFProfile();
ServiceResult result = getServiceTransport().getServiceResult();
if (table.isValid()) {
// convert from ISFTable to ISFProfile
Bundle map = result.getMap();
map.putIntArray("times", table.getTimes());
map.putFloatArray("rates", table.getRates());
map.putString("ValidDate", TimeFormat.standardFormatter().print(table.getValidDate()));
result.setMap(map);
result.setResultOK();
getServiceTransport().setServiceResult(result);
}
}
}

View file

@ -0,0 +1,29 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.ServiceData.ReadPumpClockResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/9/16.
*/
public class ReadPumpClockTask extends PumpTask {
private static final String TAG = "ReadPumpClockTask";
public ReadPumpClockTask() { }
public ReadPumpClockTask(ServiceTransport transport) {
super(transport);
}
@Override
public void run() {
ReadPumpClockResult pumpResponse = RoundtripService.getInstance().pumpManager.getPumpRTC();
if (pumpResponse != null) {
Log.i(TAG, "ReadPumpClock: " + pumpResponse.getTimeString());
} else {
Log.e(TAG, "handleServiceCommand(" + mTransport.getOriginalCommandName() + ") pumpResponse is null");
}
getServiceTransport().setServiceResult(pumpResponse);
}
}

View file

@ -0,0 +1,37 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.Page;
import com.gxwtech.roundtrip2.ServiceData.RetrieveHistoryPageResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/9/16.
*/
public class RetrieveHistoryPageTask extends PumpTask {
public RetrieveHistoryPageTask() { }
public RetrieveHistoryPageTask(ServiceTransport transport) {
super(transport);
}
private Page page;
private RetrieveHistoryPageResult result;
private int pageNumber;
@Override
public void preOp() {
// This is to avoid allocating any memory from async thread, though I'm not sure it's necessary.
RetrieveHistoryPageResult result = new RetrieveHistoryPageResult();
getServiceTransport().setServiceResult(result);
}
@Override
public void run() {
pageNumber = mTransport.getServiceCommand().getMap().getInt("pageNumber");
page = RoundtripService.getInstance().pumpManager.getPumpHistoryPage(pageNumber);
result = (RetrieveHistoryPageResult) getServiceTransport().getServiceResult();
result.setResultOK();
result.setPageNumber(pageNumber);
result.setPageBundle(page.pack());
}
}

View file

@ -0,0 +1,50 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.os.AsyncTask;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/9/16.
*/
public class ServiceTask implements Runnable {
private static final String TAG = "ServiceTask(base)";
protected ServiceTransport mTransport;
public boolean completed = false;
public ServiceTask() {
init(new ServiceTransport());
}
public ServiceTask(ServiceTransport transport) {
init(transport);
}
public void init(ServiceTransport transport) {
mTransport = transport;
}
@Override
public void run() {
}
public void preOp() {
// This function is called by UI thread before running asynch thread.
}
public void postOp() {
// This function is called by UI thread after running asynch thread.
}
public ServiceTransport getServiceTransport() {
return mTransport;
}
/*
protected void sendResponse(ServiceResult result) {
RoundtripService.getInstance().sendServiceTransportResponse(mTransport,result);
}
*/
}

View file

@ -0,0 +1,42 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 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 ServiceTask startTask(ServiceTask task) {
instance.execute(task); // task will be run on async thread from pool.
return task;
}
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());
RoundtripService.getInstance().setCurrentTask(task);
task.preOp();
}
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());
RoundtripService.getInstance().finishCurrentTask(task);
}
}

View file

@ -0,0 +1,58 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.RileyLink.PumpManagerStatus;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.ServiceData.PumpStatusResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceResult;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* Created by geoff on 7/16/16.
*/
public class UpdatePumpStatusTask extends PumpTask {
private static final String TAG = "UpdatePumpStatusTask";
public UpdatePumpStatusTask() { }
public UpdatePumpStatusTask(ServiceTransport transport) {
super(transport);
}
@Override
public void run() {
// force pump to update everything it can
RoundtripService.getInstance().pumpManager.updatePumpManagerStatus();
// get the newly cached status
PumpManagerStatus status = RoundtripService.getInstance().pumpManager.getPumpManagerStatus();
// fill a PumpStatusResult message with the goods
PumpStatusResult result = new PumpStatusResult();
/*
double remainBattery;
double remainUnits;
double currentBasal;
double lastBolusAmount;
String lastBolusTime;
int tempBasalInProgress;
double tempBasalRatio;
double tempBasalRemainMin;
String tempBasalStart;
String time;
String timeLastSync;
*/
result.setRemainBattery(status.remainBattery);
result.setRemainUnits(status.remainUnits);
result.setCurrentBasal(status.currentBasal);
result.setLastBolusAmount(status.last_bolus_amount);
result.setLastBolusTime(status.last_bolus_time.toString());
result.setTempBasalInProgress(status.tempBasalInProgress);
result.setTempBasalRatio(status.tempBasalRatio);
result.setTempBasalRemainMin(status.tempBasalRemainMin);
result.setTempBasalStart(status.tempBasalStart.toString());
result.setTime(status.time.toString());
result.setTimeLastSync("");
getServiceTransport().setServiceResult(result);
}
}

View file

@ -0,0 +1,22 @@
package com.gxwtech.roundtrip2.RoundtripService.Tasks;
import com.gxwtech.roundtrip2.RoundtripService.RoundtripService;
import com.gxwtech.roundtrip2.ServiceData.ServiceTransport;
/**
* 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() {
RoundtripService.getInstance().pumpManager.wakeup(6);
RoundtripService.getInstance().pumpManager.tuneForPump();
}
}

View file

@ -0,0 +1,23 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 6/2/16.
*/
public class ButtonPressCarelinkMessageBody extends CarelinkLongMessageBody {
public static final byte BUTTON_EASY = 0x00;
public static final byte BUTTON_ESC = 0x01;
public static final byte BUTTON_ACT = 0x02;
public static final byte BUTTON_UP = 0x03;
public static final byte BUTTON_DOWN = 0x04;
public ButtonPressCarelinkMessageBody(int which) {
init(which);
}
public void init(int buttonType) {
int numArgs = 1;
super.init(new byte[] {(byte)numArgs,(byte)buttonType});
}
}

View file

@ -0,0 +1,36 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 6/2/16.
*/
public class CarelinkLongMessageBody extends MessageBody {
public static final int LONG_MESSAGE_BODY_LENGTH = 65;
protected byte[] data;
public CarelinkLongMessageBody() {
init(new byte[0]);
}
public CarelinkLongMessageBody(byte[] payload) {
init(payload);
}
public void init(byte[] rxData) {
data = new byte[LONG_MESSAGE_BODY_LENGTH];
if (rxData != null) {
int size = rxData.length < LONG_MESSAGE_BODY_LENGTH ? rxData.length : LONG_MESSAGE_BODY_LENGTH;
for (int i=0; i<size; i++) {
data[i] = rxData[i];
}
}
}
public int getLength() {
return LONG_MESSAGE_BODY_LENGTH;
}
public byte[] getTxData() {
return data;
}
}

View file

@ -0,0 +1,38 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class CarelinkShortMessageBody extends MessageBody {
byte[] body;
public int getLength() {
return body.length;
}
public CarelinkShortMessageBody() { init(new byte[] {0}); }
public CarelinkShortMessageBody(byte[] data) {
init(data);
}
public void init(byte[] rxData) {
body = rxData;
}
public byte[] getRxData() {
return body;
}
public void setRxData(byte[] rxData) {
init(rxData);
}
public byte[] getTxData() {
return body;
}
public void setTxData(byte[] txData) {
init(txData);
}
}

View file

@ -0,0 +1,50 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
import com.gxwtech.roundtrip2.util.ByteUtil;
/**
* Created by geoff on 6/2/16.
*/
public class GetHistoryPageCarelinkMessageBody extends CarelinkLongMessageBody {
//public boolean wasLastFrame = false;
//public int frameNumber = 0;
//public byte[] frame = new byte[] {};
public GetHistoryPageCarelinkMessageBody(byte[] frameData) {
init(frameData);
}
public GetHistoryPageCarelinkMessageBody(int pageNum) {
init(pageNum);
}
public int getLength() {
return data.length;
}
public void init(byte[] rxData) {
super.init(rxData);
}
public void init(int pageNum) {
byte numArgs = 1;
super.init(new byte[] {numArgs,(byte)pageNum});
}
public int getFrameNumber() {
if (data.length > 0) {
return data[0] & 0x7f;
}
return 255;
}
public boolean wasLastFrame() {
return (data[0] & 0x80) != 0;
}
public byte[] getFrameData() {
return ByteUtil.substring(data,1,data.length-1);
}
}

View file

@ -0,0 +1,31 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class GetPumpModelCarelinkMessageBody extends MessageBody {
public int getLength() {
return 1;
}
public void init(byte[] rxData) {
}
public byte[] getRxData() {
return new byte[] { 0 };
}
public void setRxData(byte[] rxData) {
}
public byte[] getTxData() {
return new byte[] { 0 };
}
public void setTxData(byte[] txData) {
}
}

View file

@ -0,0 +1,10 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class MessageBody {
public int getLength() { return 0; }
public void init(byte[] rxData) { }
public byte[] getTxData() { return new byte[] {}; }
}

View file

@ -0,0 +1,105 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class MessageType {
public static final byte Invalid = (byte)0x00;
public static final byte Alert = (byte)0x01;
public static final byte AlertCleared = (byte)0x02;
public static final byte DeviceTest = (byte)0x03;
public static final byte PumpStatus = (byte)0x04;
public static final byte PumpAck = (byte)0x06;
public static final byte PumpBackfill = (byte)0x08;
public static final byte FindDevice = (byte)0x09;
public static final byte DeviceLink = (byte)0x0a;
public static final byte ChangeTime = (byte)0x40;
public static final byte Bolus = (byte)0x42;
public static final byte ChangeTempBasal = (byte)0x4c;
public static final byte ButtonPress = (byte)0x5b;
public static final byte PowerOn = (byte)0x5d;
public static final byte ReadTime = (byte)0x70;
public static final byte GetBattery = (byte)0x72;
public static final byte GetHistoryPage = (byte)0x80;
public static final byte GetISFProfile = (byte)0x8b;
public static final byte GetPumpModel = (byte)0x8d;
public static final byte ReadTempBasal = (byte)0x98;
public static final byte ReadSettings = (byte)0xc0;
// The above codes include codes that are not 522/722 specific.
// The codes below here are Medtronic pump specific.
// from Roundtrip.Carelink:
public static final byte CMD_M_PACKET_LENGTH = ((byte)7); // 0x07
public static final byte CMD_M_BEGIN_PARAMETER_SETTING = ((byte)38); // 0x26
public static final byte CMD_M_END_PARAMETER_SETTING = ((byte)39); // 0x27
public static final byte CMD_M_SET_A_PROFILE = ((byte)48); // 0x30
public static final byte CMD_M_SET_B_PROFILE = ((byte)49); // 0x31
public static final byte CMD_M_SET_LOGIC_LINK_ID = ((byte)50); // 0x32
public static final byte CMD_M_SET_LOGIC_LINK_ENABLE = ((byte)51); // 0x33
public static final byte CMD_M_SET_RTC = ((byte)64); // 0x40
public static final byte CMD_M_SET_MAX_BOLUS = ((byte)65); // 0x41
public static final byte CMD_M_BOLUS = ((byte)66); // 0x42
public static final byte CMD_M_SET_VAR_BOLUS_ENABLE = ((byte)69); // 0x45
public static final byte CMD_M_SET_CURRENT_PATTERN = ((byte)74); // 0x4a
public static final byte CMD_M_TEMP_BASAL_RATE = ((byte)76); // 0x4c
public static final byte CMD_M_SUSPEND_RESUME = ((byte)77); // 0x4d
public static final byte CMD_M_SET_AUTO_OFF = ((byte)78); // 0x4e
public static final byte CMD_M_SET_EASY_BOLUS_ENABLE = ((byte)79); // 0x4f
public static final byte CMD_M_SET_RF_REMOTE_ID = ((byte)81); // 0x51
public static final byte CMD_M_SET_BLOCK_ENABLE = ((byte)82); // 0x52
public static final byte CMD_M_SET_ALERT_TYPE = ((byte)84); // 0x54
public static final byte CMD_M_SET_PATTERNS_ENABLE = ((byte)85); // 0x55
public static final byte CMD_M_SET_RF_ENABLE = ((byte)87); // 0x57
public static final byte CMD_M_SET_INSULIN_ACTION_TYPE = ((byte)88); // 0x58
public static final byte CMD_M_KEYPAD_PUSH = ((byte)91); // 0x5b
public static final byte CMD_M_SET_TIME_FORMAT = ((byte)92); // 0x5c
public static final byte CMD_M_POWER_CTRL = ((byte)93); // 0x5d
public static final byte CMD_M_SET_BOLUS_WIZARD_SETUP = ((byte)94); // 0x5e
public static final byte CMD_M_SET_BG_ALARM_ENABLE = ((byte)103);// 0x67
public static final byte CMD_M_SET_TEMP_BASAL_TYPE = ((byte)104);// 0x68
public static final byte CMD_M_SET_RESERVOIR_WARNING = ((byte)106);// 0x6a
public static final byte CMD_M_SET_BG_ALARM_CLOCKS = ((byte)107);// 0x6b
public static final byte CMD_M_SET_BG_REMINDER_ENABLE = ((byte)108);// 0x6c
public static final byte CMD_M_SET_MAX_BASAL = ((byte)110);// 0x6e
public static final byte CMD_M_SET_STD_PROFILE = ((byte)111);// 0x6f
public static final byte CMD_M_READ_RTC = ((byte)112);// 0x70
public static final byte CMD_M_READ_PUMP_ID = ((byte)113);// 0x71
public static final byte CMD_M_READ_INSULIN_REMAINING = ((byte)115);// 0x73
public static final byte CMD_M_READ_FIRMWARE_VER = ((byte)116);// 0x74
public static final byte CMD_M_READ_ERROR_STATUS = ((byte)117);// 0x75
public static final byte CMD_M_READ_REMOTE_CTRL_IDS = ((byte)118);// 0x76
public static final byte CMD_M_READ_HISTORY = ((byte)128);// 0x80
public static final byte CMD_M_READ_PUMP_STATE = ((byte)131);// 0x83
public static final byte CMD_M_READ_BOLUS_WIZARD_SETUP_STATUS = ((byte)135);// 0x87
public static final byte CMD_M_READ_CARB_UNITS = ((byte)136);// 0x88
public static final byte CMD_M_READ_BG_UNITS = ((byte)137);// 0x89
public static final byte CMD_M_READ_CARB_RATIOS = ((byte)138);// 0x8a
public static final byte CMD_M_READ_INSULIN_SENSITIVITIES = ((byte)139);// 0x8b
public static final byte CMD_M_READ_BG_TARGETS = ((byte)140);// 0x8c
public static final byte CMD_M_READ_PUMP_MODEL_NUMBER = ((byte)141);// 0x8d
public static final byte CMD_M_READ_BG_ALARM_CLOCKS = ((byte)142);// 0x8e
public static final byte CMD_M_READ_RESERVOIR_WARNING = ((byte)143);// 0x8f
public static final byte CMD_M_READ_BG_REMINDER_ENABLE = ((byte)144);// 0x90
public static final byte CMD_M_READ_SETTINGS = ((byte)145);// 0x91
public static final byte CMD_M_READ_STD_PROFILES = ((byte)146);// 0x92
public static final byte CMD_M_READ_A_PROFILES = ((byte)147);// 0x93
public static final byte CMD_M_READ_B_PROFILES = ((byte)148);// 0x94
public static final byte CMD_M_READ_LOGIC_LINK_IDS = ((byte)149);// 0x95
public static final byte CMD_M_READ_BG_ALARM_ENABLE = ((byte)151);// 0x97
public static final byte CMD_M_READ_TEMP_BASAL = ((byte)152);// 0x98
public static final byte CMD_M_READ_PUMP_SETTINGS = ((byte)192);// 0xc0
public static final byte CMD_M_READ_PUMP_STATUS = ((byte)206);// 0xce
public byte mtype;
public MessageType(byte mtype) {
this.mtype = mtype;
}
public static MessageBody constructMessageBody(MessageType messageType, byte[] bodyData) {
switch (messageType.mtype) {
case PumpAck: return new PumpAckMessageBody(bodyData);
default: return new UnknownMessageBody(bodyData);
}
}
}

View file

@ -0,0 +1,11 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class PumpAckMessageBody extends CarelinkShortMessageBody {
public PumpAckMessageBody() { init(new byte[] {0}); }
public PumpAckMessageBody(byte[] bodyData) {
init(bodyData);
}
}

View file

@ -0,0 +1,35 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.Messages;
/**
* Created by geoff on 5/29/16.
*/
public class UnknownMessageBody extends MessageBody {
public byte[] rxData;
public int getLength() {
return 0;
}
public UnknownMessageBody(byte[] data) {
this.rxData = data;
}
public void init(byte[] rxData) {
}
public byte[] getRxData() {
return rxData;
}
public void setRxData(byte[] rxData) {
this.rxData = rxData;
}
public byte[] getTxData() {
return rxData;
}
public void setTxData(byte[] txData) {
this.rxData = txData;
}
}

View file

@ -0,0 +1,20 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic;
/**
* Created by geoff on 5/29/16.
*/
public class PacketType {
public static final short Invalid = 0x00;
public static final short MySentry = 0xa2;
public static final short Meter = 0xa5;
public static final short Carelink = 0xa7;
public static final short Sensor = 0xa8;
public short value = 0;
public PacketType() {
}
public PacketType(short value) {
this.value = value;
}
}

View file

@ -0,0 +1,175 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import android.util.Log;
import org.joda.time.Instant;
import java.util.ArrayList;
/**
* Created by geoff on 6/1/15.
*
* There are three basal profiles stored on the pump. (722 only?)
* They are all parsed the same, the user just has 3 to choose from:
* Standard, A, and B
*
* The byte array seems to be 21 three byte entries long, plus a zero?
* If the profile is completely empty, it should have one entry: [0,0,0x3F] (?)
* The first entry of [0,0,0] marks the end of the used entries.
*
* Each entry is assumed to span from the specified start time to the start time of the
* next entry, or to midnight if there are no more entries.
*
* Individual entries are of the form [r,z,m] where
* r is the rate (in 0.025 U increments)
* z is zero (?)
* m is the start time-of-day for the basal rate period (in 30 minute increments?)
*/
public class BasalProfile {
private static final String TAG = "BasalProfile";
private static final boolean DEBUG_BASALPROFILE = false;
protected static final int MAX_RAW_DATA_SIZE = (21 * 3) + 1;
protected byte[] mRawData; // store as byte array to make transport (via parcel) easier
public BasalProfile() {
init();
}
public void init() {
mRawData = new byte[MAX_RAW_DATA_SIZE];
mRawData[0] = 0;
mRawData[1] = 0;
mRawData[2] = 0x3f;
}
// this asUINT8 should be combined with Record.asUINT8, and placed in a new util class.
protected static int readUnsignedByte(byte b) { return (b<0)?b+256:b; }
public boolean setRawData(byte[] data) {
if (data == null) {
Log.e(TAG,"setRawData: buffer is null!");
return false;
}
int len = Math.min(MAX_RAW_DATA_SIZE, data.length);
System.arraycopy(data, 0, mRawData, 0, len);
if (DEBUG_BASALPROFILE) {
Log.v(TAG, String.format("setRawData: copied raw data buffer of %d bytes.", len));
}
return true;
}
public void dumpBasalProfile() {
Log.v(TAG, "Basal Profile entries:");
ArrayList<BasalProfileEntry> entries = getEntries();
for (int i=0; i< entries.size(); i++) {
BasalProfileEntry entry = entries.get(i);
String startString = entry.startTime.toString("HH:mm");
Log.v(TAG, String.format("Entry %d, rate=%.3f (0x%02X), start=%s (0x%02X)",
i+1, entry.rate, entry.rate_raw,
startString, entry.startTime_raw));
}
}
// TODO: this function must be expanded to include changes in which profile is in use.
// and changes to the profiles themselves.
public BasalProfileEntry getEntryForTime(Instant when) {
BasalProfileEntry rval = new BasalProfileEntry();
ArrayList<BasalProfileEntry> entries = getEntries();
if (entries.size() == 0) {
Log.w(TAG, String.format("getEntryForTime(%s): table is empty",
when.toDateTime().toLocalTime().toString("HH:mm")));
return rval;
}
//Log.w(TAG,"Assuming first entry");
rval = entries.get(0);
if (entries.size() == 1) {
Log.v(TAG,"getEntryForTime: Only one entry in profile");
return rval;
}
int localMillis = when.toDateTime().toLocalTime().getMillisOfDay();
boolean done = false;
int i=1;
while (!done) {
BasalProfileEntry entry = entries.get(i);
if (DEBUG_BASALPROFILE) {
Log.v(TAG, String.format("Comparing 'now'=%s to entry 'start time'=%s",
when.toDateTime().toLocalTime().toString("HH:mm"),
entry.startTime.toString("HH:mm")));
}
if (localMillis >= entry.startTime.getMillisOfDay()) {
rval = entry;
if (DEBUG_BASALPROFILE) Log.v(TAG,"Accepted Entry");
} else {
// entry at i has later start time, keep older entry
if (DEBUG_BASALPROFILE) Log.v(TAG,"Rejected Entry");
done = true;
}
i++;
if (i >= entries.size()) {
done = true;
}
}
if (DEBUG_BASALPROFILE) {
Log.v(TAG, String.format("getEntryForTime(%s): Returning entry: rate=%.3f (%d), start=%s (%d)",
when.toDateTime().toLocalTime().toString("HH:mm"),
rval.rate, rval.rate_raw,
rval.startTime.toString("HH:mm"), rval.startTime_raw));
}
return rval;
}
public ArrayList<BasalProfileEntry> getEntries() {
ArrayList<BasalProfileEntry> entries = new ArrayList<>();
if (mRawData[2] == 0x3f) {
Log.w(TAG,"Raw Data is empty.");
return entries; // an empty list
}
int i = 0;
boolean done = false;
int r,st;
while (!done) {
r = readUnsignedByte(mRawData[i]);
// What is mRawData[i+1]? Not used in decocare.
st = readUnsignedByte(mRawData[i+2]);
entries.add(new BasalProfileEntry(r,st));
i=i+3;
if (i>=MAX_RAW_DATA_SIZE) {
done=true;
} else if ((mRawData[i]==0) && (mRawData[i+1]==0) && (mRawData[i+2]==0)) {
done = true;
}
}
return entries;
}
public static void testParser() {
byte[] testData = new byte[] {
32, 0, 0,
38, 0, 13,
44, 0, 19,
38, 0, 28,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
/* from decocare:
_test_schedule = {'total': 22.50, 'schedule': [
{ 'start': '12:00A', 'rate': 0.80 },
{ 'start': '6:30A', 'rate': 0.95 },
{ 'start': '9:30A', 'rate': 1.10 },
{ 'start': '2:00P', 'rate': 0.95 },
]}
*/
BasalProfile profile = new BasalProfile();
profile.setRawData(testData);
ArrayList<BasalProfileEntry> entries = profile.getEntries();
if (entries.isEmpty()) {
Log.e(TAG,"testParser: failed");
} else {
for (int i=0; i<entries.size(); i++) {
BasalProfileEntry e = entries.get(i);
Log.d(TAG, String.format("testParser entry #%d: rate: %.2f, start %d:%d",
i,e.rate,e.startTime.getHourOfDay(),
e.startTime.getMinuteOfHour()));
}
}
}
}

View file

@ -0,0 +1,28 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import org.joda.time.LocalTime;
/**
* Created by geoff on 6/1/15.
* This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile
*/
public class BasalProfileEntry {
public byte rate_raw;
public double rate;
public byte startTime_raw;
public LocalTime startTime; // Just a "time of day"
public BasalProfileEntry() {
rate = -9.999E6;
rate_raw = (byte)0xFF;
startTime = new LocalTime(0);
startTime_raw = (byte)0xFF;
}
public BasalProfileEntry(int rateByte, int startTimeByte) {
// rateByte is insulin delivery rate, U/hr, in 0.025 U increments
// startTimeByte is time-of-day, in 30 minute increments
rate_raw = (byte)rateByte;
rate = rateByte * 0.025;
startTime_raw = (byte)startTimeByte;
startTime = new LocalTime(startTimeByte / 2, (startTimeByte % 2) * 30);
}
}

View file

@ -0,0 +1,8 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/1/15.
*/
public enum BasalProfileTypeEnum {
STD,A,B;
}

View file

@ -0,0 +1,31 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.BolusWizardBolusEstimatePumpEvent;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TempBasalEvent;
import java.util.ArrayList;
/**
* Created by geoff on 6/5/15.
*
* This class is inteded to gather what information we've gleaned from the pump history
* into one place, make it easier to move around.
*
*/
@Deprecated
public class HistoryReport {
public ArrayList<BolusWizardBolusEstimatePumpEvent> mBolusWizardEvents;
public ArrayList<TempBasalEvent> mBasalEvents;
public HistoryReport() {
mBolusWizardEvents = new ArrayList<>();
mBasalEvents = new ArrayList<>();
}
public void addBolusWizardEvent(BolusWizardBolusEstimatePumpEvent event) {
mBolusWizardEvents.add(event);
}
public void addTempBasalEvent(TempBasalEvent event)
{
mBasalEvents.add(event);
}
}

View file

@ -0,0 +1,11 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
public class HtmlByte extends HtmlElement {
private byte data = 0;
public HtmlByte() {}
public HtmlByte(byte b) { data = b; }
public String toString() {return String.format("%02x",data);}
}

View file

@ -0,0 +1,9 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
public class HtmlCodeTagEnd extends HtmlElement {
public HtmlCodeTagEnd() {}
public String toString() { return "</code>"; }
}

View file

@ -0,0 +1,30 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
public class HtmlCodeTagStart extends HtmlElement {
String title = "";
String color = "";
public HtmlCodeTagStart(String title, String bgColor) {
this.title = title;
this.color = bgColor;
}
public String toString() {
StringBuilder b = new StringBuilder();
b.append("<code");
if ((title != null) && (title.length() > 0)) {
b.append(" title=\"");
b.append(title);
b.append("\"");
}
if ((color != null) && (color.length() > 0)) {
b.append(" style=\"background-color:");
b.append(color);
b.append(";\"");
}
b.append(">");
return b.toString();
}
}

View file

@ -0,0 +1,9 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
abstract public class HtmlElement {
public HtmlElement() {}
abstract public String toString();
}

View file

@ -0,0 +1,15 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
public class HtmlElementGeneric extends HtmlElement {
String s = "";
public HtmlElementGeneric() {}
public HtmlElementGeneric(String s) {
this.s = s;
}
public String toString() {
return s;
}
}

View file

@ -0,0 +1,15 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 6/24/16.
*/
public class HtmlHistoryPageStart extends HtmlElement {
int pagenum = 0;
public HtmlHistoryPageStart() {}
public HtmlHistoryPageStart(int num) {
pagenum = num;
}
public String toString() {
return "<h2>History Page " + pagenum + "</h2>\n";
}
}

View file

@ -0,0 +1,102 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import com.gxwtech.roundtrip2.util.ByteUtil;
import org.joda.time.LocalDateTime;
/**
* Created by geoff on 7/2/16.
*
* This class exists to map the Minimed byte response to "Get sensitivity factors"
* into a more usable form.
*/
public class ISFTable {
boolean mIsValid = false;
LocalDateTime validDate;
byte[] header;
int[] times;
float[] rates;
public ISFTable() {}
public boolean parseFrom(byte[] responseBytes) {
// example format: { 7, 1, 0, 45, 12, 30, 42, 50, 0, 0 }
// means value pairs: {0, 45}, {12, 30}, {42, 50}
// means: at midnight, the amount is 45,
// at 6am, the about is 30
// at 9pm, the amount is 50
if (responseBytes == null) return false;
// minimum of two bytes in header
if (responseBytes.length < 2) {
return false;
}
// Must be an even number of times and rates
if (responseBytes.length % 2 != 0) {
return false;
}
mIsValid = true;
header = ByteUtil.substring(responseBytes,0,2);
// find end of list
int index = 2;
while (true) {
if (index + 1 > responseBytes.length) {
break;
}
if ((responseBytes[index]==0) && (responseBytes[index+1]==0)) {
break;
}
index += 2;
}
if (index == 2) {
// no entries.
times = new int[]{};
rates = new float[]{};
} else {
int numEntries = (index - 2) / 2;
times = new int[numEntries];
rates = new float[numEntries];
for (int i=0; i<numEntries; i++) {
times[i] = responseBytes[i*2+2];
rates[i] = responseBytes[i*2+2];
}
}
validDate = new LocalDateTime();
return true;
}
public byte[] getHeader() {
return header;
}
public void setHeader(byte[] header) {
this.header = header;
}
public int[] getTimes() {
return times;
}
public void setTimes(int[] times) {
this.times = times;
}
public float[] getRates() {
return rates;
}
public void setRates(float[] rates) {
this.rates = rates;
}
public LocalDateTime getValidDate() {
return validDate;
}
public void setValidDate(LocalDateTime validDate) {
this.validDate = validDate;
}
public boolean isValid() {
return mIsValid;
}
}

View file

@ -0,0 +1,409 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 5/13/15.
*
* This class was taken from medtronic-android-uploader.
* This class was written such that the constructors did all the work, which resulted
* in annoyances such as exceptions during constructors.
*
* TODO: This class needs to be revisited and probably rewritten. (2016-06-12)
*
*
*
Pete Schwamb
@ps2
12:04
History entries will not reorder themselves, but some events do update, like dual wave bolus entries
It's like an append only log, and when a page is full, it rotates the page ids and starts appending to a new blank page
Darrell Wright
@beached
12:05
so the timestamp is entry creation not update
time
Pete Schwamb
@ps2
12:06
Yes, I don't think the timestamps ever change
GGW: TODO: examine src/ecc1/medtronic for better history parsing
*/
import android.os.Bundle;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.BolusWizardBolusEstimatePumpEvent;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.Record;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.RecordTypeEnum;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.TempBasalDurationPumpEvent;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.TempBasalRatePumpEvent;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.CRC;
import com.gxwtech.roundtrip2.util.HexDump;
import org.joda.time.DateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Page {
private final static String TAG = "Page";
private static final boolean DEBUG_PAGE = true;
private byte[] crc;
private byte[] data;
protected PumpModel model;
public List<Record> mRecordList;
public Page() {
this.model = PumpModel.UNSET;
mRecordList = new ArrayList<>();
}
public byte[] getRawData() {
if (data == null) {
return crc;
}
if (crc == null) {
return data;
}
return ByteUtil.concat(data,crc);
}
protected PumpTimeStamp collectTimeStamp(byte[] data, int offset) {
try {
PumpTimeStamp timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, offset));
return timestamp;
} catch (org.joda.time.IllegalFieldValueException e) {
return null;
}
}
public boolean parsePicky(byte[] rawPage, PumpModel model) {
mRecordList = new ArrayList<>();
this.model = model;
int pageOffset = 0;
if ((rawPage == null) || (rawPage.length == 0)) return false;
this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2);
this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length);
byte[] expectedCrc = CRC.calculate16CCITT(this.data);
if (DEBUG_PAGE) {
Log.i(TAG, String.format("Data length: %d", data.length));
}
if (!Arrays.equals(crc, expectedCrc)) {
Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc)));
} else {
if (DEBUG_PAGE) {
Log.i(TAG, "CRC OK");
}
}
Record record = null;
while (pageOffset < data.length) {
if (data[pageOffset] == 0) {
if (record != null) {
Log.i(TAG,String.format("End of page or Previous parse fail: prev opcode 0x%02x, curr offset %d, %d bytes remaining",
record.getRecordOp(),pageOffset,data.length - pageOffset + 1));
break;
} else {
Log.i(TAG, "WTF?");
}
}
try {
record = attemptParseRecord(data, pageOffset);
} catch (org.joda.time.IllegalFieldValueException e) {
record = null;
}
if (record == null) {
Log.i(TAG, "PARSE FAIL");
pageOffset++;
} else {
mRecordList.add(record);
pageOffset+=record.getLength();
}
}
ArrayList<Record> pickyRecords = new ArrayList<>();
pickyRecords.addAll(mRecordList);
parseByDates(rawPage,model);
for (Record r : mRecordList) {
for (Record r2 : pickyRecords) {
if (r.getFoundAtOffset() == r2.getFoundAtOffset()) {
Log.v(TAG,"Found matching record at offset " + r.getFoundAtOffset());
}
}
}
return true;
}
public boolean parseByDates(byte[] rawPage, PumpModel model) {
mRecordList = new ArrayList<>();
if (rawPage.length != 1024) {
Log.e(TAG,"Unexpected page size. Expected: 1024 Was: " + rawPage.length);
//return false;
}
this.model = model;
if (DEBUG_PAGE) {
Log.i(TAG, "Parsing page");
}
if (rawPage.length < 4) {
Log.e(TAG,"Page too short, need at least 4 bytes");
return false;
}
this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2);
this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length);
byte[] expectedCrc = CRC.calculate16CCITT(this.data);
if (DEBUG_PAGE) {
Log.i(TAG, String.format("Data length: %d", data.length));
}
if (!Arrays.equals(crc, expectedCrc)) {
Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc)));
} else {
if (DEBUG_PAGE) {
Log.i(TAG, "CRC OK");
}
}
int pageOffset = 0;
PumpTimeStamp lastPumpTimeStamp = new PumpTimeStamp();
while (pageOffset < this.data.length - 7) {
PumpTimeStamp timestamp = collectTimeStamp(data,pageOffset+2);
if (timestamp!=null) {
String year = timestamp.toString().substring(0,3);
Record record;
if ("201".equals(year)) {
// maybe found a record.
try {
record = attemptParseRecord(data, pageOffset);
} catch (org.joda.time.IllegalFieldValueException e) {
record = null;
}
if (record != null) {
if (timestamp.getLocalDateTime().compareTo(lastPumpTimeStamp.getLocalDateTime()) >= 0) {
Log.i(TAG, "Timestamp is increasing");
lastPumpTimeStamp = timestamp;
mRecordList.add(record);
} else {
Log.e(TAG, "Timestamp is decreasing");
}
}
}
}
pageOffset++;
}
return true;
}
public boolean parseFrom(byte[] rawPage, PumpModel model) {
mRecordList = new ArrayList<>(); // wipe old contents each time when parsing.
if (rawPage.length != 1024) {
Log.e(TAG,"Unexpected page size. Expected: 1024 Was: " + rawPage.length);
//return false;
}
this.model = model;
if (DEBUG_PAGE) {
Log.i(TAG, "Parsing page");
}
if (rawPage.length < 4) {
Log.e(TAG,"Page too short, need at least 4 bytes");
return false;
}
this.data = Arrays.copyOfRange(rawPage, 0, rawPage.length-2);
this.crc = Arrays.copyOfRange(rawPage, rawPage.length-2, rawPage.length);
byte[] expectedCrc = CRC.calculate16CCITT(this.data);
if (DEBUG_PAGE) {
Log.i(TAG, String.format("Data length: %d", data.length));
}
if (!Arrays.equals(crc, expectedCrc)) {
Log.w(TAG, String.format("CRC does not match expected value. Expected: %s Was: %s", HexDump.toHexString(expectedCrc), HexDump.toHexString(crc)));
} else {
if (DEBUG_PAGE) {
Log.i(TAG, "CRC OK");
}
}
int dataIndex = 0;
boolean done = false;
while (!done) {
Record record = null;
if (data[dataIndex] != 0) {
// If the data byte is zero, assume that means end of page
try {
record = attemptParseRecord(data, dataIndex);
} catch (org.joda.time.IllegalFieldValueException e) {
record = null;
}
} else {
Log.v(TAG,"Zero opcode encountered -- end of page. " + (rawPage.length - dataIndex) + " bytes remaining.");
break;
}
if (record != null) {
Log.v(TAG,"parseFrom: found event "+record.getClass().getSimpleName() + " length=" + record.getLength() + " offset=" + record.getFoundAtOffset());
mRecordList.add(record);
dataIndex += record.getLength();
} else {
Log.e(TAG,String.format("parseFrom: Failed to parse opcode 0x%02x, offset=%d",data[dataIndex],dataIndex));
done = true;
}
if (dataIndex >= data.length - 2) {
done = true;
}
}
if (DEBUG_PAGE) {
Log.i(TAG, String.format("Number of records: %d", mRecordList.size()));
int index = 1;
for (Record r : mRecordList) {
Log.v(TAG, String.format("Record #%d: %s", index,r.getShortTypeName()));
index += 1;
}
}
return true;
}
/* attemptParseRecord will attempt to create a subclass of Record from the given
* data and offset. It will return NULL if it fails. If it succeeds, the returned
* subclass of Record can be examined for its length, so that the next attempt can be made.
*/
public static <T extends Record> T attemptParseRecord(byte[] data, int offsetStart) {
// no data?
if (data == null) {
return null;
}
// invalid offset?
if (data.length < offsetStart) {
return null;
}
//Log.d(TAG,String.format("checking for handler for record type 0x%02X at index %d",data[offsetStart],offsetStart));
RecordTypeEnum en = RecordTypeEnum.fromByte(data[offsetStart]);
T record = en.getRecordClassInstance(PumpModel.MM522);
if (record != null) {
// have to do this to set the record's opCode
byte[] tmpData = new byte[data.length];
System.arraycopy(data, offsetStart, tmpData, 0, data.length - offsetStart);
boolean didParse = record.parseWithOffset(tmpData, PumpModel.MM522, offsetStart);
if (!didParse) {
Log.e(TAG,String.format("attemptParseRecord: class %s (opcode 0x%02X) failed to parse at offset %d",record.getShortTypeName(),data[offsetStart],offsetStart));
}
}
return record;
}
public static DateTime parseSimpleDate(byte[] data, int offset) {
DateTime timeStamp = null;
int seconds = 0;
int minutes = 0;
int hour = 0;
//int high = data[0] >> 4;
int low = data[0 + offset] & 0x1F;
//int year_high = data[1] >> 4;
int mhigh = (data[0 + offset] & 0xE0) >> 4;
int mlow = (data[1 + offset] & 0x80) >> 7;
int month = mhigh + mlow;
int dayOfMonth = low + 1;
// python code says year is data[1] & 0x0F, but that will cause problem in 2016.
// Hopefully, the remaining bits are part of the year...
int year = data[1 + offset] & 0x3F;
/*
Log.w(TAG, String.format("Attempting to create DateTime from: %04d-%02d-%02d %02d:%02d:%02d",
year + 2000, month, dayOfMonth, hour, minutes, seconds));
*/
try {
timeStamp = new DateTime(year + 2000, month, dayOfMonth, hour, minutes, seconds);
} catch (org.joda.time.IllegalFieldValueException e) {
//Log.e(TAG,"Illegal DateTime field");
//e.printStackTrace();
return null;
}
return timeStamp;
}
public static void discoverRecords(byte[] data) {
int i = 0;
boolean done = false;
ArrayList<Integer> keyLocations= new ArrayList();
while (!done) {
RecordTypeEnum en = RecordTypeEnum.fromByte(data[i]);
if (en != RecordTypeEnum.RECORD_TYPE_NULL) {
keyLocations.add(i);
Log.v(TAG, String.format("Possible record of type %s found at index %d", en, i));
}
/*
DateTime ts = parseSimpleDate(data,i);
if (ts != null) {
if (ts.year().get() == 2015) {
Log.w(TAG, String.format("Possible simple date at index %d", i));
}
}
*/
i = i + 1;
done = (i >= data.length-2);
}
// for each of the discovered key locations, attempt to parse a sequence of records
for(RecordTypeEnum en : RecordTypeEnum.values()) {
}
for (int ix = 0; ix < keyLocations.size(); ix++) {
}
}
/*
*
* For IPC serialization
*
*/
/*
private byte[] crc;
private byte[] data;
protected PumpModel model;
public List<Record> mRecordList;
*/
public Bundle pack() {
Bundle bundle = new Bundle();
bundle.putByteArray("crc",crc);
bundle.putByteArray("data",data);
bundle.putString("model",PumpModel.toString(model));
ArrayList<Bundle> records = new ArrayList<>();
for (int i=0; i<mRecordList.size(); i++) {
try {
records.add(mRecordList.get(i).dictionaryRepresentation());
} catch (NullPointerException e) {
e.printStackTrace();
}
}
bundle.putParcelableArrayList("mRecordList",records);
return bundle;
}
public void unpack(Bundle in) {
crc = in.getByteArray("crc");
data = in.getByteArray("data");
model = PumpModel.fromString(in.getString("model"));
ArrayList<Bundle> records = in.getParcelableArrayList("mRecordList");
mRecordList = new ArrayList<>();
if (records != null) {
for (int i=0; i<records.size(); i++) {
Record r = RecordTypeEnum.getRecordClassInstance(records.get(i),model);
r.readFromBundle(records.get(i));
mRecordList.add(r);
}
}
}
}

View file

@ -0,0 +1,51 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import android.content.ContentValues;
import android.os.Bundle;
/**
* Created by geoff on 6/17/16.
*/
public class PumpHistoryDatabaseEntry {
public static final String key_timestamp = "timestamp";
private String timestampString;
public static final String key_pageNum = "pagenum";
private int pageNum;
public static final String key_pageOffset = "offset";
private int pageOffset;
public static final String key_recordType = "type";
private String recordType;
public static final String key_length = "length";
private int length;
public static String getTableInitString() {
String rval = "(";
rval += "id INTEGER PRIMARY KEY" + ", ";
rval += key_timestamp + " TEXT" + ",";
rval += key_pageNum + " INTEGER" + ",";
rval += key_pageOffset + " INTEGER" + ",";
rval += key_recordType + " TEXT" + ",";
rval += key_length + " INTEGER" +")";
return rval;
}
public PumpHistoryDatabaseEntry() {
}
public boolean initFromRecordBundle(int pageNum, Bundle bundle) {
timestampString = bundle.getString("timestamp","00-00-00T00:00:00");
this.pageNum = pageNum;
pageOffset = bundle.getInt("foundAtOffset",-1);
recordType = bundle.getString("_type","(unknown)");
this.length = bundle.getInt("length",0);
return true;
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put(key_length,length);
values.put(key_pageNum,pageNum);
values.put(key_pageOffset,pageOffset);
values.put(key_recordType,recordType);
values.put(key_timestamp,timestampString);
return values;
}
}

View file

@ -0,0 +1,57 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.util.List;
/**
* Created by geoff on 6/17/16.
*/
public class PumpHistoryDatabaseHandler extends SQLiteOpenHelper {
private static final String TAG = "PumpHistoryDatabase";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "RT2PumpHistory";
private static final String DATABASE_TABLE_entries = "entries";
public PumpHistoryDatabaseHandler(Context context) {
super(context,DATABASE_NAME,null,DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String CREATE_TABLE_entries = "CREATE TABLE " + DATABASE_TABLE_entries + PumpHistoryDatabaseEntry.getTableInitString();
sqLiteDatabase.execSQL(CREATE_TABLE_entries);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int old_version, int new_version) {
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS "+DATABASE_TABLE_entries);
onCreate(sqLiteDatabase);
}
public void addEntry(PumpHistoryDatabaseEntry entry) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = entry.getContentValues();
db.insert(DATABASE_TABLE_entries, null, values);
db.close();
}
public void addContentValuesList(List<ContentValues> list) {
SQLiteDatabase db = getWritableDatabase();
for (ContentValues cvs : list) {
db.insert(DATABASE_TABLE_entries, null, cvs);
}
db.close();
Log.d(TAG,"Database "+ DATABASE_NAME + " saved");
}
public void clearPumpHistoryDatabase() {
SQLiteDatabase db = getWritableDatabase();
db.execSQL("DROP TABLE IF EXISTS "+DATABASE_TABLE_entries);
onCreate(db);
db.close();
}
}

View file

@ -0,0 +1,341 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import android.content.ContentValues;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import com.gxwtech.roundtrip2.RT2Const;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records.Record;
import com.gxwtech.roundtrip2.util.StringUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
/**
* Created by geoff on 6/17/16.
*/
public class PumpHistoryManager {
private static final String TAG = "PumpHistoryManager";
private Context context;
private PumpHistoryDatabaseHandler phdb;
ArrayList<ContentValues> dbContentValues = new ArrayList<>();
ArrayList<Bundle> packedPages = null;
public PumpHistoryManager(Context context) {
this.context = context;
phdb = new PumpHistoryDatabaseHandler(context);
}
public void initFromPages(Bundle historyBundle) {
packedPages = historyBundle.getParcelableArrayList(RT2Const.IPC.MSG_PUMP_history_key);
for (int i=0; i<packedPages.size(); i++) {
Bundle pageBundle = packedPages.get(i);
if (pageBundle != null) {
ArrayList<Bundle> recordBundleList = pageBundle.getParcelableArrayList("mRecordList");
for (Bundle b : recordBundleList) {
try {
PumpHistoryDatabaseEntry entry = new PumpHistoryDatabaseEntry();
if (entry.initFromRecordBundle(i, b)) {
dbContentValues.add(entry.getContentValues());
}
} catch (java.lang.NullPointerException e) {
e.printStackTrace();
}
}
}
}
phdb.addContentValuesList(dbContentValues);
}
public void clearDatabase() {
phdb.clearPumpHistoryDatabase();
}
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
public boolean timestampOK(String timestamp) {
if (timestamp == null) {
return false;
}
if (timestamp.length() < 4) {
return false;
}
if ("2015".equals(timestamp.substring(0,4))) {
return true;
}
if ("2016".equals(timestamp.substring(0,4))) {
return true;
}
return false;
}
public HtmlCodeTagStart renderContexts(ArrayList<Bundle> relevantBundles) {
// search for all contexts that span this byte
// make a start tag that represents them.
StringBuilder titleBuilder = new StringBuilder();
for (Bundle b : relevantBundles) {
String name = b.getString("_type");
String timestamp = b.getString("timestamp");
int length = b.getInt("length");
int offset = b.getInt("foundAtOffset");
titleBuilder.append(String.format("[%s%s %s l=%d o=%d]",
timestampOK(timestamp) ? "" : "BAD ",
name==null?"(null)":name,
timestamp == null?"(null)":timestamp,
length, offset));
}
String colorString = null;
if (relevantBundles.size() == 1) {
if (timestampOK(relevantBundles.get(0).getString("timestamp"))) {
colorString = "#99ff99";
} else {
colorString = "#F0E68C";
}
} else if (relevantBundles.size() > 1) {
boolean allOK = relevantBundlesAllOK(relevantBundles);
boolean allBad = relevantBundlesAllBad(relevantBundles);
if (allOK) {
colorString = "#248f24";
} else if (allBad) {
colorString = "#cc0000";
} else {
colorString = "#b3b300";
}
}
return new HtmlCodeTagStart(titleBuilder.toString(),colorString);
}
public ArrayList<Bundle> findRelevantBundles(int pageNum, int pageOffset) {
ArrayList<Bundle> relevantBundles = new ArrayList<>();
ArrayList<Bundle> recordBundleList = packedPages.get(pageNum).getParcelableArrayList("mRecordList");
for (int i=0; i< recordBundleList.size(); i++) {
Bundle recordBundle = recordBundleList.get(i);
int offset = recordBundle.getInt("foundAtOffset");
int length = recordBundle.getInt("length");
if ((offset <= pageOffset) && (offset + length > pageOffset)) {
relevantBundles.add(recordBundle);
}
}
return relevantBundles;
}
public boolean relevantBundlesAllOK(ArrayList<Bundle> bundles) {
for (Bundle b : bundles) {
String ts = b.getString("timestamp");
if (ts == null) {
return false;
}
if (!timestampOK(ts)) {
return false;
}
}
return true;
}
public boolean relevantBundlesAllBad(ArrayList<Bundle> bundles) {
for (Bundle b : bundles) {
String ts = b.getString("timestamp");
if (ts != null) {
if (timestampOK(ts)) {
return false;
}
}
}
return true;
}
public boolean relevantBundlesSame(ArrayList<Bundle> bundles1, ArrayList<Bundle> bundles2) {
if (bundles1.size() != bundles2.size()) {
return false;
}
for (Bundle b : bundles1) {
if (!bundles2.contains(b)) {
return false;
}
}
for (Bundle b : bundles2) {
if (!bundles1.contains(b)) {
return false;
}
}
return true;
}
public boolean relevantBundlesAllDifferent(ArrayList<Bundle> bundles1, ArrayList<Bundle> bundles2) {
for (Bundle b : bundles1) {
if (bundles2.contains(b)) {
return false;
}
}
for (Bundle b : bundles2) {
if (bundles1.contains(b)) {
return false;
}
}
return true;
}
public ArrayList<HtmlElement> makeDom() {
ArrayList<HtmlElement> rval = new ArrayList<>();
for (int pageNum = 0; pageNum < packedPages.size(); pageNum++) {
Log.i(TAG,"Rendering page " + pageNum);
rval.add(new HtmlHistoryPageStart(pageNum));
byte[] pageData = packedPages.get(pageNum).getByteArray("data");
if (pageData == null) {
return rval;
}
int pageSize = pageData.length;
byte[] crc = packedPages.get(pageNum).getByteArray("crc");
if (pageSize != 1022) {
Log.e(TAG, "Page size is not 1022, it is " + pageSize);
}
int pageOffset = 0;
boolean done = false;
ArrayList<Bundle> currentBundles = findRelevantBundles(pageNum, pageOffset);
ArrayList<Bundle> nextBundles = new ArrayList<>();
rval.add(renderContexts(currentBundles));
while (!done) {
rval.add(new HtmlByte(pageData[pageOffset]));
if (pageOffset == pageSize - 1) {
done = true;
} else {
nextBundles = findRelevantBundles(pageNum, pageOffset + 1);
if (relevantBundlesSame(currentBundles, nextBundles)) {
// Not changing context
if (pageOffset % 32 == 31) {
rval.add(new HtmlElementGeneric("<br>\n"));
} else {
rval.add(new HtmlElementGeneric(" "));
}
} else {
// Changing context
rval.add(new HtmlCodeTagEnd());
if (relevantBundlesAllDifferent(currentBundles, nextBundles)) {
rval.add(new HtmlCodeTagStart("", "")); // <code>
if (pageOffset % 32 == 31) {
rval.add(new HtmlElementGeneric("<br>\n"));
} else {
rval.add(new HtmlElementGeneric(" "));
}
if (nextBundles.size() != 0) {
rval.add(new HtmlCodeTagEnd());
rval.add(renderContexts(nextBundles));
}
} else {
if (pageOffset % 32 == 31) {
rval.add(new HtmlElementGeneric("<br>\n"));
} else {
rval.add(new HtmlElementGeneric(" "));
}
rval.add(new HtmlCodeTagEnd());
rval.add(renderContexts(nextBundles));
}
}
}
if (!done) {
pageOffset++;
currentBundles = nextBundles;
}
}
}
return rval;
}
public void writeHtmlPage() {
/*
final String key_timestamp = "timestamp";
String timestampString;
final String key_pageNum = "pagenum";
int pageNum;
final String key_pageOffset = "offset";
int foundAtOffset;
final String key_recordType = "type";
String recordType;
final String key_length = "length";
int length;
*/
if (!isExternalStorageWritable()) {
Log.e(TAG,"External storage not writable.");
return;
}
String filename = "PumpHistoryBytes.html";
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
File pageFile = new File(path,filename);
OutputStream os = null;
try {
os = new FileOutputStream(pageFile);
} catch (FileNotFoundException fnf) {
Log.e(TAG,"Failed to open " + filename + " for writing");
}
if (os == null) {
return;
}
// write header
try {
os.write("<!DOCTYPE html>".getBytes());
os.write("<html>".getBytes());
os.write("<head>".getBytes());
os.write("<title>Page Title</title>".getBytes());
os.write("</head>".getBytes());
os.write("<body>".getBytes());
byte[] pageData = packedPages.get(0).getByteArray("data");
int pageSize = pageData.length;
//byte[] crc = packedPages.get(0).getByteArray("crc");
if (pageSize != 1022) {
Log.e(TAG,"Page size is not 1022, it is " + pageSize);
}
ArrayList<HtmlElement> dom = makeDom();
Log.i(TAG,"There are " + dom.size() + " elements to render.");
for (HtmlElement e : dom) {
if (e != null) {
String elementString = e.toString();
if (elementString != null) {
byte[] bytes = elementString.getBytes();
if (bytes != null) {
os.write(bytes);
} else {
Log.e(TAG,"WriteHtmlPage: bytes is null");
}
} else {
Log.e(TAG,"WriteHtmlPage: elementString is null");
}
} else {
Log.e(TAG,"WriteHtmlPage: element is null");
}
}
os.write("</body></html>".getBytes());
os.close();
} catch (FileNotFoundException fnf) {
fnf.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,43 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.util.ByteUtil;
import com.gxwtech.roundtrip2.util.CRC;
/**
* Created by geoff on 6/18/16.
*/
public class PumpHistoryPage {
public byte[] data = new byte[0];
private PumpModel model = PumpModel.MM522;
private int pageNumber;
public PumpHistoryPage() {}
public PumpHistoryPage(byte[] data, PumpModel model, int pageNumber) {
init(data,model,pageNumber);
}
public void init(byte[] data, PumpModel model, int pageNumber) {
this.data = data;
this.model = model;
this.pageNumber = pageNumber;
}
public int getPageNumber() { return pageNumber; }
public boolean isValid() {
return isCRCValid();
}
public boolean isCRCValid() {
if (data == null) {
return false;
}
if (data.length < 3) {
return false;
}
byte[] crc16 = CRC.calculate16CCITT(ByteUtil.substring(data,0,data.length-2));
if (crc16 == null) {
return false;
}
if ((crc16[0] == data[data.length-2]) && (crc16[1]==data[data.length-1])) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,263 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
import android.os.Bundle;
import android.util.Log;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat;
import com.gxwtech.roundtrip2.util.ByteUtil;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Created by geoff on 6/18/16.
*
*
*
* This class is not in use -- it may become a replacement for the history object heirarchy parser
*
*
*
*
*
*
*
*/
@Deprecated
public class PumpHistoryParser {
private static final String TAG = "PumpHistoryParser";
public PumpModel model;
public ArrayList<PumpHistoryPage> pages = new ArrayList<>();
public ArrayList<Bundle> entries = new ArrayList<>();
public PumpHistoryParser(PumpModel model) {
initOpcodes();
}
public void parsePage(byte[] rawData) {
PumpHistoryPage page = new PumpHistoryPage(rawData,model,pages.size()+1);
pages.add(page);
if (page.isCRCValid() == false) {
Log.e(TAG,"CRC16 for page " + page.getPageNumber() + " is invalid.");
}
if (page.data.length != 1024) {
Log.w(TAG,String.format("Page %d has length %d, expected 1024",page.getPageNumber(),page.data.length));
}
int currentOffset = 0;
boolean done = false;
while (!done) {
Bundle b = attemptParseRecord(page.getPageNumber(),currentOffset);
if (b == null) {
// then the parse failed.
}
}
}
static int asUINT8(byte b) {
return (b < 0) ? b + 256 : b;
}
static double insulinDecode(int a, int b) {
return ((a << 8) + b) / 40.0;
}
public Bundle attemptParseRecord(int pageNum, int offset) {
byte[] dataPage = pages.get(pageNum).data;
int opcode = pages.get(pageNum).data[offset];
Bundle record = new Bundle();
String typename = opCodeNames.get(opcode);
if (typename != null) {
record.putInt("opcode",opcode);
record.putString("type",typename);
record.putInt("page",pageNum);
record.putInt("offset",offset);
record.putString("indexer",String.format("%02d%04d",pageNum,offset));
} else {
return null;
}
int length = 0;
int dateOffset = 0;
PumpTimeStamp timestamp;
byte[] data = ByteUtil.substring(dataPage,offset,dataPage.length - 2 - offset);
switch (opcode) {
case RECORD_TYPE_BolusNormal: {
length = PumpModel.isLargerFormat(model) ? 13 : 9;
if (length + offset > data.length) return null;
record.putInt("length",length);
double programmedAmount;
double deliveredAmount;
double unabsorbedInsulinTotal;
int duration;
String bolusType;
if (PumpModel.isLargerFormat(model)) {
programmedAmount = insulinDecode(asUINT8(data[1]),asUINT8(data[2]));
deliveredAmount = insulinDecode(asUINT8(data[3]),asUINT8(data[4]));
unabsorbedInsulinTotal = insulinDecode(asUINT8(data[5]),asUINT8(data[6]));
duration = asUINT8(data[7]) * 30;
try {
timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 8));
} catch (org.joda.time.IllegalFieldValueException e) {
timestamp = new PumpTimeStamp();
}
} else {
programmedAmount = asUINT8(data[1]) / 10.0f;
deliveredAmount = asUINT8(data[2]) / 10.0f;
duration = asUINT8(data[3]) * 30;
unabsorbedInsulinTotal = 0;
try {
timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 4));
} catch (org.joda.time.IllegalFieldValueException e) {
timestamp = new PumpTimeStamp();
}
}
bolusType = (duration > 0) ? "square" : "normal";
record.putDouble("programmedAmount",programmedAmount);
record.putDouble("deliveredAmount",deliveredAmount);
record.putInt("duration",duration);
record.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal);
record.putString("bolusType",bolusType);
record.putString("timestamp",timestamp.getLocalDateTime().toString());
} break;
case RECORD_TYPE_AlarmSensor:
length = 8;
if (length + offset > data.length) return null;
record.putInt("length",length);
case RECORD_TYPE_Resume:
case RECORD_TYPE_AlarmClockReminder:
length = 7;
if (length + offset > data.length) return null;
record.putInt("length",length);
try {
timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 2));
} catch (org.joda.time.IllegalFieldValueException e) {
timestamp = new PumpTimeStamp();
}
record.putString("timestamp",timestamp.getLocalDateTime().toString());
default:
record.putInt("length",0);
record.putString("comment","unparsed");
}
return record;
}
HashMap<Integer,String> opCodeNames = new HashMap<>();
HashMap<String,Integer> opCodeNumbers = new HashMap<>();
private void initOpcode(int number, String name) {
opCodeNames.put(number,name);
opCodeNumbers.put(name,number);
}
public static final int RECORD_TYPE_BolusNormal = 0x01;
public static final int RECORD_TYPE_Prime = 0x03;
public static final int RECORD_TYPE_PumpAlarm = 0x06;
public static final int RECORD_TYPE_ResultDailyTotal = 0x07;
public static final int RECORD_TYPE_ChangeBasalProfilePattern = 0x08;
public static final int RECORD_TYPE_ChangeBasalProfile = 0x09;
public static final int RECORD_TYPE_CalBgForPh = 0x0A;
public static final int RECORD_TYPE_AlarmSensor = 0x0B;
public static final int RECORD_TYPE_ClearAlarm = 0x0C;
public static final int RECORD_TYPE_SelectBasalProfile = 0x14;
public static final int RECORD_TYPE_TempBasalDuration = 0x16;
public static final int RECORD_TYPE_ChangeTime = 0x17;
public static final int RECORD_TYPE_NewTimeSet = 0x18;
public static final int RECORD_TYPE_JournalEntryPumpLowBattery = 0x19;
public static final int RECORD_TYPE_Battery = 0x1A;
public static final int RECORD_TYPE_Suspend = 0x1E;
public static final int RECORD_TYPE_Resume = 0x1F;
public static final int RECORD_TYPE_Rewind = 0x21;
public static final int RECORD_TYPE_ChangeChildBlockEnable = 0x23;
public static final int RECORD_TYPE_ChangeMaxBolus = 0x24;
public static final int RECORD_TYPE_EnableDisableRemote = 0x26;
public static final int RECORD_TYPE_TempBasalRate = 0x33;
public static final int RECORD_TYPE_JournalEntryPumpLowReservoir = 0x34;
public static final int RECORD_TYPE_AlarmClockReminder = 0x35;
public static final int RECORD_TYPE_BGReceived = 0x3F;
public static final int RECORD_TYPE_JournalEntryExerciseMarker = 0x41;
public static final int RECORD_TYPE_ChangeSensorSetup2 = 0x50;
public static final int RECORD_TYPE_ChangeSensorRateOfChangeAlertSetup = 0x56;
public static final int RECORD_TYPE_ChangeBolusScrollStepSize = 0x57;
public static final int RECORD_TYPE_ChangeBolusWizardSetup = 0x5A;
public static final int RECORD_TYPE_BolusWizardBolusEstimate = 0x5B;
public static final int RECORD_TYPE_UnabsorbedInsulin = 0x5C;
public static final int RECORD_TYPE_ChangeVariableBolus = 0x5e;
public static final int RECORD_TYPE_ChangeAudioBolus = 0x5f;
public static final int RECORD_TYPE_ChangeBGReminderEnable = 0x60;
public static final int RECORD_TYPE_ChangeAlarmClockEnable = 0x61;
public static final int RECORD_TYPE_ChangeTempBasalType = 0x62;
public static final int RECORD_TYPE_ChangeAlarmNotifyMode = 0x63;
public static final int RECORD_TYPE_ChangeTimeFormat = 0x64;
public static final int RECORD_TYPE_ChangeReservoirWarningTime = 0x65;
public static final int RECORD_TYPE_ChangeBolusReminderEnable = 0x66;
public static final int RECORD_TYPE_ChangeBolusReminderTime = 0x67;
public static final int RECORD_TYPE_DeleteBolusReminderTime = 0x68;
public static final int RECORD_TYPE_DeleteAlarmClockTime = 0x6a;
public static final int RECORD_TYPE_Model522ResultTotals = 0x6D;
public static final int RECORD_TYPE_Sara6E = 0x6E;
public static final int RECORD_TYPE_ChangeCarbUnits = 0x6f;
public static final int RECORD_TYPE_BasalProfileStart = 0x7B;
public static final int RECORD_TYPE_ChangeWatchdogEnable = 0x7c;
public static final int RECORD_TYPE_ChangeOtherDeviceID = 0x7d;
public static final int RECORD_TYPE_ChangeWatchdogMarriageProfile = 0x81;
public static final int RECORD_TYPE_DeleteOtherDeviceID = 0x82;
public static final int RECORD_TYPE_ChangeCaptureEventEnable = 0x83;
private void initOpcodes() {
initOpcode(0x01,"BolusNormal");
initOpcode(0x03,"Prime");
initOpcode(0x06,"PumpAlarm");
initOpcode(0x07,"ResultDailyTotal");
initOpcode(0x08,"ChangeBasalProfilePattern");
initOpcode(0x09,"ChangeBasalProfile");
initOpcode(0x0A,"CalBgForPh");
initOpcode(0x0B,"AlarmSensor");
initOpcode(0x0C,"ClearAlarm");
//initOpcode(0x14,"SelectBasalProfile");
initOpcode(0x16,"TempBasalDuration");
initOpcode(0x17,"ChangeTime");
//initOpcode(0x18,"NewTimeSet");
initOpcode(0x19,"JournalEntryPumpLowBattery");
initOpcode(0x1A,"Battery");
initOpcode(0x1E,"Suspend");
initOpcode(0x1F,"Resume");
initOpcode(0x21,"Rewind");
initOpcode(0x23,"ChangeChildBlockEnable");
initOpcode(0x24,"ChangeMaxBolus");
initOpcode(0x26,"EnableDisableRemote");
initOpcode(0x33,"TempBasalRate");
initOpcode(0x34,"JournalEntryPumpLowReservoir");
initOpcode(0x35,"AlarmClockReminder");
initOpcode(0x3F,"BGReceived");
initOpcode(0x41,"JournalEntryExerciseMarker");
initOpcode(0x50,"ChangeSensorSetup2");
initOpcode(0x56,"ChangeSensorRateOfChangeAlertSetup");
initOpcode(0x57,"ChangeBolusScrollStepSize");
initOpcode(0x5A,"ChangeBolusWizardSetup");
initOpcode(0x5B,"BolusWizardBolusEstimate");
initOpcode(0x5C,"UnabsorbedInsulin");
initOpcode(0x5e,"ChangeVariableBolus");
initOpcode(0x5f,"ChangeAudioBolus");
initOpcode(0x60,"ChangeBGReminderEnable");
initOpcode(0x61,"ChangeAlarmClockEnable");
initOpcode(0x62,"ChangeTempBasalType");
initOpcode(0x63,"ChangeAlarmNotifyMode");
initOpcode(0x64,"ChangeTimeFormat");
initOpcode(0x65,"ChangeReservoirWarningTime");
initOpcode(0x66,"ChangeBolusReminderEnable");
initOpcode(0x67,"ChangeBolusReminderTime");
initOpcode(0x68,"DeleteBolusReminderTime");
initOpcode(0x6a,"DeleteAlarmClockTime");
initOpcode(0x6D,"Model522ResultTotals");
initOpcode(0x6E,"Sara6E");
initOpcode(0x6f,"ChangeCarbUnits");
initOpcode(0x7B,"BasalProfileStart");
initOpcode(0x7c,"ChangeWatchdogEnable");
initOpcode(0x7d,"ChangeOtherDeviceID");
initOpcode(0x81,"ChangeWatchdogMarriageProfile");
initOpcode(0x82,"DeleteOtherDeviceID");
initOpcode(0x83,"ChangeCaptureEventEnable");
}
}

View file

@ -0,0 +1,168 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 5/7/15.
*
* This class is intended to be very basic, and only hold the simple data.
* That way it will be portable, reusable.
*
* For instance, for the android UI, there is a parcelable version of this class
* (PumpSettingsParcel) but I don't want the pump implementation to be dependent
* on Android.Parcelable.
*
* For marshalling the data into other forms, I'm keeping the original
* byte array (mRawData) around. So if we have to save/reload this data object
* we can just write the raw data array, and reload from it. We already have to
* parse our values from it, so just keep it handy. Makes the Parcelable
* implementation very simple.
*
*/
@Deprecated
public class PumpSettings {
private static final String TAG = "PumpSettings";
// these values help with the parcel extension
protected static final int MINIMUM_DATA_LENGTH = 20; // bytes
protected static final int MAXIMUM_DATA_LENGTH = 64; // bytes
protected byte[] mRawData;
public byte mAutoOffDuration_hours = 0;
public byte mAlarmMode = 0;
public byte mAlarmVolume = 0;
public boolean mAudioBolusEnable = false;
public double mAudioBolusSize = 0.0;
public boolean mVariableBolusEnable = false;
public double mMaxBolus = 0.0; // MM23 is different (?)
// MM512 and up (?)
public double mMaxBasal = 0.0;
public byte mTimeFormat = 0;
public int mInsulinConcentration = 100; // 100 or 50
public boolean mPatternsEnabled = false;
public BasalProfileTypeEnum mSelectedPattern = BasalProfileTypeEnum.STD;
public boolean mRFEnable = false;
public boolean mBlockEnable = false;
public byte mTempBasalType = 0; // 1 means Percent, 0 means UnitsPerHour
public byte mTempBasalRate = 0; // TODO: scaling?
public byte mParadigmEnable = 0;
// MM12
// boolean mInsulinActionType (0='Fast' or 1='Regular')
// MM15
public byte mInsulinActionType = 0;
public byte mLowReservoirWarnType = 0;
public byte mLowReservoirWarnPoint = 0;
public byte mKeypadLockStatus = 0;
public PumpSettings() {
init();
}
public void init() {
// this data buffer has to be sized to maximum even when empty
// as we may be reading from a parcel. See PumpSettingsParcel.
mRawData = new byte[MAXIMUM_DATA_LENGTH];
/* fill in default values */
}
public byte[] getRawData() { return mRawData; }
public boolean parseFrom(byte[] data) {
if (!setRawData(data)) { return false; }
return parseFromRaw();
}
// copy as much as we can from a byte array, but don't parse it.
public boolean setRawData(byte[] data) {
if (data == null) {
return false;
}
int len = Math.min(MAXIMUM_DATA_LENGTH,data.length);
System.arraycopy(data,0,mRawData,0,len);
return true;
}
public boolean parseFromRaw() {
mAutoOffDuration_hours = mRawData[0];
mAlarmVolume = mRawData[1];
mAlarmMode = 2;
if (mRawData[1] == 4) {
mAlarmVolume = -1;
mAlarmMode = 1;
}
mAudioBolusEnable = (mRawData[2] == 1);
mAudioBolusSize = 0;
if (mAudioBolusEnable) {
mAudioBolusSize = mRawData[3] / 10.0;
}
mVariableBolusEnable = (mRawData[4] == 1);
// MM23 is different
int maxBolusByte = mRawData[5];
if (maxBolusByte < 0) { maxBolusByte += 256; }
mMaxBolus = (maxBolusByte) / 10.0;
// MM512 and up
// TODO: check mMaxBasal calculation
// did I get the bytes in the right order?
int maxBasalHighByte = mRawData[6];
if (maxBasalHighByte < 0) {
maxBasalHighByte += 256;
}
int maxBasalLowByte = mRawData[7];
if (maxBasalLowByte < 0) {
maxBasalLowByte += 256;
}
mMaxBasal = ((maxBasalHighByte * 256) + maxBasalLowByte)/40.0;
mTimeFormat = mRawData[8];
// mInsulinConcentration: 0 is 100%, 1 is 50%
mInsulinConcentration = 100;
if (mRawData[9] == 1) {
mInsulinConcentration = 50;
}
mPatternsEnabled = (mRawData[10] == 1);
mSelectedPattern = BasalProfileTypeEnum.STD;
if (mRawData[11] == 0x01) {
mSelectedPattern = BasalProfileTypeEnum.A;
} else if (mRawData[11] == 0x02) {
mSelectedPattern = BasalProfileTypeEnum.B;
}
mRFEnable = (mRawData[12] == 1);
mBlockEnable = (mRawData[13] == 1);
mTempBasalType = mRawData[14]; // todo: put into proper class of its own
mTempBasalRate = mRawData[15];
mParadigmEnable = mRawData[16];
mInsulinActionType = mRawData[17];
mLowReservoirWarnType = mRawData[18];
mLowReservoirWarnPoint = mRawData[19];
mKeypadLockStatus = mRawData[20];
return true;
}
public String explain() {
String rval = "";
// write out a string describing the current contents
rval += String.format("Auto-Off Duration (hours): %d\n",mAutoOffDuration_hours);
rval += String.format("Alarm (Volume: %d, Mode: %d)\n",mAlarmVolume,mAlarmMode);
rval += String.format("Audio BolusNormalPumpEvent (enabled=%s, size=%g)\n",mAudioBolusEnable,mAudioBolusSize);
rval += String.format("Variable BolusNormalPumpEvent Enable: %s\n", mVariableBolusEnable);
rval += String.format("Max BolusNormalPumpEvent: %g\n",mMaxBolus);
rval += String.format("Max Basal: %g\n",mMaxBasal);
rval += String.format("Time Format 0x%02X\n", mTimeFormat);
rval += String.format("Insulin Concentration %d%%\n",mInsulinConcentration);
rval += String.format("Patterns Enabled: %s\n",mPatternsEnabled);
rval += String.format("Selected Pattern: %d\n",mSelectedPattern);
rval += String.format("RF Enabled: %s\n",mRFEnable);
rval += String.format("Block Enabled: %s\n",mBlockEnable);
rval += String.format("Temp Basal Type: %s\n",(mTempBasalType == 1)?"Percent":"Units/Hr");
if (mTempBasalType == 1) {
rval += String.format("Temp Basal Rate: %d%%\n",mTempBasalRate);
} else {
rval += String.format("Temp Basal Rate: %d U/h\n",mTempBasalRate);
}
rval += String.format("Paradigm Enabled: %s\n", mParadigmEnable);
rval += String.format("Insulin Action Type: 0x%02X\n",mInsulinActionType);
rval += String.format("Low Reservoir Warn Type: 0x%02X\n",mLowReservoirWarnType);
rval += String.format("Low Reservoir Warn Point: 0x%02X\n",mLowReservoirWarnPoint);
rval += String.format("Keypad Lock Status: 0x%02X\n",mKeypadLockStatus);
return rval;
}
}

View file

@ -0,0 +1,45 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData;
/**
* Created by geoff on 5/29/15.
*
* Just need a class to keep the pair together, for parcel transport.
*/
@Deprecated
public class TempBasalPair {
private double mInsulinRate = 0.0;
private int mDurationMinutes = 0;
private boolean mIsPercent = false;
public double getInsulinRate() {
return mInsulinRate;
}
public void setInsulinRate(double insulinRate) {
this.mInsulinRate = insulinRate;
}
public int getDurationMinutes() {
return mDurationMinutes;
}
public void setDurationMinutes(int durationMinutes) {
this.mDurationMinutes = durationMinutes;
}
public boolean isPercent() {
return mIsPercent;
}
public void setIsPercent(boolean yesIsPercent) {
this.mIsPercent = yesIsPercent;
}
public TempBasalPair() { }
public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) {
mInsulinRate = insulinRate;
mIsPercent = isPercent;
mDurationMinutes = durationMinutes;
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/11/16.
*/
public class AlarmClockReminderPumpEvent extends TimeStampedRecord {
public AlarmClockReminderPumpEvent() {};
@Override
public String getShortTypeName() {
return "Alarm Reminder";
}
}

View file

@ -0,0 +1,19 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
/**
* Created by geoff on 6/5/16.
*/
public class AlarmSensorPumpEvent extends TimeStampedRecord {
public AlarmSensorPumpEvent() {}
@Override
public int getLength() { return 8; }
@Override
public String getShortTypeName() {
return "Alarm Sensor";
}
}

View file

@ -0,0 +1,47 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.util.ByteUtil;
public class BGReceivedPumpEvent extends TimeStampedRecord {
private int amount = 0;
private byte[] meter = new byte[3];
public BGReceivedPumpEvent() {
}
@Override
public int getLength() { return 10; }
@Override
public String getShortTypeName() {
return "BG Received";
}
@Override
public boolean parseFrom(byte[] data, PumpModel model) {
if (!super.simpleParse(data,2)) {
return false;
}
amount = (asUINT8(data[1]) << 3) + (asUINT8(data[4])>>5);
meter = ByteUtil.substring(data,7,3);
return true;
}
@Override
public boolean readFromBundle(Bundle in) {
amount = in.getInt("amount");
meter = in.getByteArray("meter");
return super.readFromBundle(in);
}
@Override
public void writeToBundle(Bundle in) {
super.writeToBundle(in);
in.putInt("amount",amount);
in.putByteArray("meter",meter);
}
}

View file

@ -0,0 +1,51 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
public class BasalProfileStart extends TimeStampedRecord {
private static final String TAG = "BasalProfileStart";
private int offset = 0;
private double rate = 0.0;
private int profileIndex = 0;
public BasalProfileStart() { }
@Override
public int getLength() { return 10; }
@Override
public String getShortTypeName() {
return "Basal Profile Start";
}
@Override
public boolean parseFrom(byte[] data, PumpModel model) {
if (!simpleParse(data,2)) {
return false;
}
profileIndex = asUINT8(data[1]);
offset = asUINT8(data[7]) * 30 * 1000 * 60;
rate = (double)(asUINT8(data[8])) / 40.0;
return true;
}
@Override
public boolean readFromBundle(Bundle in) {
offset = in.getInt("offset");
rate = in.getDouble("rate");
profileIndex = in.getInt("profileIndex");
return super.readFromBundle(in);
}
@Override
public void writeToBundle(Bundle in) {
super.writeToBundle(in);
in.putInt("offset",offset);
in.putDouble("rate",rate);
in.putInt("profileIndex",profileIndex);
}
}

View file

@ -0,0 +1,10 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
public class BatteryPumpEvent extends TimeStampedRecord {
public BatteryPumpEvent() {}
@Override
public String getShortTypeName() {
return "Battery";
}
}

View file

@ -0,0 +1,86 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpTimeStamp;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.TimeFormat;
public class BolusNormalPumpEvent extends TimeStampedRecord {
private final static String TAG = "BolusNormalPumpEvent";
private double programmedAmount = 0.0;
private double deliveredAmount = 0.0;
private int duration = 0;
private double unabsorbedInsulinTotal = 0.0;
private String bolusType = "Unset";
public BolusNormalPumpEvent() {
}
@Override
public int getLength() { return PumpModel.isLargerFormat(model) ? 13 : 9; }
@Override
public String getShortTypeName() {
return "Normal Bolus";
}
private double insulinDecode(int a, int b) {
return ((a << 8) + b) / 40.0;
}
@Override
public boolean parseFrom(byte[] data, PumpModel model) {
if (getLength() > data.length) {
return false;
}
if (PumpModel.isLargerFormat(model)) {
programmedAmount = insulinDecode(asUINT8(data[1]),asUINT8(data[2]));
deliveredAmount = insulinDecode(asUINT8(data[3]),asUINT8(data[4]));
unabsorbedInsulinTotal = insulinDecode(asUINT8(data[5]),asUINT8(data[6]));
duration = asUINT8(data[7]) * 30;
try {
timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data, 8));
} catch (org.joda.time.IllegalFieldValueException e) {
return false;
}
} else {
programmedAmount = asUINT8(data[1]) / 10.0f;
deliveredAmount = asUINT8(data[2]) / 10.0f;
duration = asUINT8(data[3]) * 30;
unabsorbedInsulinTotal = 0;
try {
timestamp = new PumpTimeStamp(TimeFormat.parse5ByteDate(data,4));
} catch (org.joda.time.IllegalFieldValueException e) {
return false;
}
}
bolusType = (duration > 0) ? "square" : "normal";
return true;
}
@Override
public boolean readFromBundle(Bundle in) {
programmedAmount = in.getDouble("programmedAmount",0.0);
deliveredAmount = in.getDouble("deliveredAmount",0.0);
duration = in.getInt("duration",0);
unabsorbedInsulinTotal = in.getDouble("unabsorbedInsulinTotal",0.0);
bolusType = in.getString("bolusType","Unset");
return super.readFromBundle(in);
}
@Override
public void writeToBundle(Bundle in) {
super.writeToBundle(in);
in.putDouble("programmedAmount",programmedAmount);
in.putDouble("deliveredAmount",deliveredAmount);
in.putInt("duration",duration);
in.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal);
in.putString("bolusType",bolusType);
}
}

View file

@ -0,0 +1,118 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
public class BolusWizardBolusEstimatePumpEvent extends TimeStampedRecord {
private final static String TAG = "BolusWizardBolusEstimatePumpEvent";
private int carbohydrates;
private int bloodGlucose;
private double foodEstimate;
private double correctionEstimate;
private double bolusEstimate;
private double unabsorbedInsulinTotal;
private int bgTargetLow;
private int bgTargetHigh;
private int insulinSensitivity;
private double carbRatio;
public BolusWizardBolusEstimatePumpEvent() {
correctionEstimate = (double)0.0;
bloodGlucose = 0;
carbohydrates = 0;
carbRatio = 0.0;
insulinSensitivity = 0;
bgTargetLow = 0;
bgTargetHigh = 0;
bolusEstimate = 0.0;
foodEstimate = 0.0;
unabsorbedInsulinTotal = 0.0;
}
@Override
public int getLength() { return PumpModel.isLargerFormat(model) ? 22 : 20;}
@Override
public String getShortTypeName() {
return "Bolus Wizard Est.";
}
@Override
public boolean readFromBundle(Bundle in) {
carbohydrates = in.getInt("carbohydrates",0);
bloodGlucose = in.getInt("bloodGlucose",0);
foodEstimate = in.getDouble("foodEstimate",0);
correctionEstimate = in.getDouble("correctionEstimate",0);
bolusEstimate = in.getDouble("bolusEstimate",0);
unabsorbedInsulinTotal = in.getDouble("unabsorbedInsulinTotal",0);
bgTargetLow = in.getInt("bgTargetLow",0);
bgTargetHigh = in.getInt("bgTargetHigh",0);
insulinSensitivity = in.getInt("insulinSensitivity",0);
carbRatio = in.getDouble("carbRatio",0);
return super.readFromBundle(in);
}
@Override
public void writeToBundle(Bundle in) {
super.writeToBundle(in);
in.putInt("carbohydrates",carbohydrates);
in.putInt("bloodGlucose",bloodGlucose);
in.putDouble("foodEstimate",foodEstimate);
in.putDouble("correctionEstimate",correctionEstimate);
in.putDouble("bolusEstimate",bolusEstimate);
in.putDouble("unabsorbedInsulinTotal",unabsorbedInsulinTotal);
in.putInt("bgTargetLow",bgTargetLow);
in.putInt("bgTargetHigh",bgTargetHigh);
in.putInt("insulinSensitivity",insulinSensitivity);
in.putDouble("carbRatio",carbRatio);
}
public double getCorrectionEstimate() { return correctionEstimate; }
public long getBG() { return bloodGlucose; }
public int getCarbohydrates() { return carbohydrates; }
public double getICRatio() { return carbRatio; }
public int getInsulinSensitivity() { return insulinSensitivity; }
public int getBgTargetLow() { return bgTargetLow; }
public int getBgTargetHigh() { return bgTargetHigh; }
public double getBolusEstimate() { return bolusEstimate; }
public double getFoodEstimate() { return foodEstimate; }
public double getUnabsorbedInsulinTotal() { return unabsorbedInsulinTotal; }
private double insulinDecode(int a, int b) {
return ((a << 8) + b) / 40.0;
}
@Override
public boolean parseFrom(byte[] data, PumpModel model) {
if (!simpleParse(data,2)) {
return false;
}
if (PumpModel.isLargerFormat(model)) {
carbohydrates = (asUINT8(data[8]) & 0x0c << 6) + asUINT8(data[7]);
bloodGlucose = (asUINT8(data[8]) & 0x03 << 8) + asUINT8(data[1]);
foodEstimate = insulinDecode(asUINT8(data[14]), asUINT8(data[15]));
correctionEstimate = (double)((asUINT8(data[16])& 0b111000) << 5 + asUINT8(data[13]))/40.0;
bolusEstimate = insulinDecode(asUINT8(data[19]), asUINT8(data[20]));
unabsorbedInsulinTotal = insulinDecode(asUINT8(data[17]),asUINT8(data[18]));
bgTargetLow = asUINT8(data[12]);
bgTargetHigh = asUINT8(data[21]);
insulinSensitivity = asUINT8(data[11]);
carbRatio = (double)(((asUINT8(data[9]) & 0x07) << 8) + asUINT8(data[10]))/40.0;
} else {
carbohydrates = asUINT8(data[7]);
bloodGlucose = ((asUINT8(data[8]) & 0x03) << 8) + asUINT8(data[1]);
foodEstimate = (double)(asUINT8(data[13]))/10.0;
correctionEstimate = (double)((asUINT8(data[14])<<8) + asUINT8(data[12])) / 10.0;
bolusEstimate = (double)(asUINT8(data[18]))/10.0;
unabsorbedInsulinTotal = (double)(asUINT8(data[16])) / 10.0;
bgTargetLow = asUINT8(data[11]);
bgTargetHigh = asUINT8(data[19]);
insulinSensitivity = asUINT8(data[10]);
carbRatio = (double)asUINT8(data[9]);
}
return true;
}
}

View file

@ -0,0 +1,39 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import android.os.Bundle;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
public class CalBgForPhPumpEvent extends TimeStampedRecord {
private int amount = 0;
public CalBgForPhPumpEvent() {
}
@Override
public String getShortTypeName() {
return "Cal Bg For Ph";
}
@Override
public boolean parseFrom(byte[] data, PumpModel model) {
if (!simpleParse(data,2)) {
return false;
}
amount = ((asUINT8(data[6]) & 0x80) << 1) + asUINT8(data[1]);
return true;
}
@Override
public boolean readFromBundle(Bundle in) {
amount = in.getInt("amount",0);
return super.readFromBundle(in);
}
@Override
public void writeToBundle(Bundle in) {
super.writeToBundle(in);
in.putInt("amount",amount);
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeAlarmClockEnablePumpEvent extends TimeStampedRecord {
public ChangeAlarmClockEnablePumpEvent() {}
@Override
public String getShortTypeName() {
return "Alarm Clock Enable";
}
}

View file

@ -0,0 +1,10 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
public class ChangeAlarmNotifyModePumpEvent extends TimeStampedRecord {
public ChangeAlarmNotifyModePumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch Alarm Notify Mode";
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeAudioBolusPumpEvent extends TimeStampedRecord {
public ChangeAudioBolusPumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch Audio Bolus";
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeBGReminderEnablePumpEvent extends TimeStampedRecord {
public ChangeBGReminderEnablePumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch BG Rmndr Enable";
}
}

View file

@ -0,0 +1,21 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeBasalProfilePatternPumpEvent extends TimeStampedRecord {
public ChangeBasalProfilePatternPumpEvent() {}
@Override
public int getLength() {
return 152;
}
@Override
public String getShortTypeName() {
return "Ch Basal Prof Pat";
}
}

View file

@ -0,0 +1,15 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
public class ChangeBasalProfilePumpEvent extends TimeStampedRecord {
public ChangeBasalProfilePumpEvent() {
}
@Override
public int getLength() { return 152; }
@Override
public String getShortTypeName() {
return "Ch Basal Profile";
}
}

View file

@ -0,0 +1,19 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeBolusReminderEnablePumpEvent extends TimeStampedRecord {
public ChangeBolusReminderEnablePumpEvent() {}
@Override
public int getLength() { return 9; }
@Override
public String getShortTypeName() {
return "Ch Bolus Rmndr Enable";
}
}

View file

@ -0,0 +1,19 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeBolusReminderTimePumpEvent extends TimeStampedRecord {
public ChangeBolusReminderTimePumpEvent(){}
@Override
public String getShortTypeName() {
return "Ch Bolus Rmndr Time";
}
@Override
public int getLength() { return 9; }
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeBolusScrollStepSizePumpEvent extends TimeStampedRecord {
public ChangeBolusScrollStepSizePumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch Bolus Scroll SS";
}
}

View file

@ -0,0 +1,21 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
import com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpModel;
public class ChangeBolusWizardSetupPumpEvent extends TimeStampedRecord {
public ChangeBolusWizardSetupPumpEvent() {
}
@Override
public int getLength() {
return 144;
}
@Override
public String getShortTypeName() {
return "Ch Bolus Wizard Setup";
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeCaptureEventEnablePumpEvent extends TimeStampedRecord {
public ChangeCaptureEventEnablePumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch Capture Event Ena";
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeCarbUnitsPumpEvent extends TimeStampedRecord {
public ChangeCarbUnitsPumpEvent() {}
@Override
public String getShortTypeName() {
return "Ch Carb Units";
}
}

View file

@ -0,0 +1,13 @@
package com.gxwtech.roundtrip2.RoundtripService.medtronic.PumpData.records;
/**
* Created by geoff on 6/5/16.
*/
public class ChangeChildBlockEnablePumpEvent extends TimeStampedRecord {
public ChangeChildBlockEnablePumpEvent(){}
@Override
public String getShortTypeName() {
return "Ch Child Block Ena";
}
}

Some files were not shown because too many files have changed in this diff Show more